当前位置: 首页>编程语言>正文

android自定义view--SearchView

前言

上一篇Path特效功臣----PathMesure我们讲了PathMesure中api的详细方法和测试。本文就用我们学到的PathMeasure实现一个动态效果的SearchView,先瞄一下好不好看

android自定义view--SearchView,第1张
searchView图片来源于网上

思路(由简到繁)

绘制静态的路径(放大镜和外圆)
1、onSizeChang()中得到组件大小
2、填充Path的放大镜和外圆
为静态图添加动态效果
1、采用ValueAnimtor提供实时变量
2、采用PathMeasure根据实时变量去绘制
3、动态效果分为四种状态(初始化,放大镜动画,外圆动画,结束动画)

绘制静态的路径

绘制的路径全部由Path去填充,放大镜可以拆分为一个圆和一条斜线,需要注意的是这里addArc的起始角度和终点角度


android自定义view--SearchView,第2张
红色为Path的起点45度
class MySearchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    constructor(context: Context?) : this(context, null)

    private val mPaint = Paint()//画笔
    private var offestFactoty = 0.9f //偏移因子画布是组件的0.9
    private var mWidth: Float = 0.0f  //组件宽度
    private var mHeight: Float = 0.0f //组件高度
    private lateinit var searchRecf: RectF
    private lateinit var cicleRecf: RectF
    private val searchPath: Path = Path() //放大镜的path
    private val ciclePath: Path = Path()  //外圆的path

    init {
        initPaint()
    }

  
    private fun initPaint() {
        mPaint.color = Color.BLUE
        mPaint.isAntiAlias = true
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeCap = Paint.Cap.ROUND
        mPaint.strokeWidth = 8f
    }
    //1、onSizeChang()中得到组件大小
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w.toFloat()
        mHeight = h.toFloat()
        initPath()
    }
    //2、填充Path的放大镜和外圆
    private fun initPath() {
        cicleRecf = RectF(-mWidth / 2 * offestFactoty, -mHeight / 2 * offestFactoty, mWidth / 2 * offestFactoty, mHeight / 2 * offestFactoty)
        searchRecf = RectF(-mWidth / 5, -mHeight / 5, mWidth / 5, mHeight / 5)
        searchPath.addArc(searchRecf, 45f, 359.9f) //填充内圆
        ciclePath.addArc(cicleRecf, 45f, 359.9f)  //填充外圆
        val pathMeasure = PathMeasure(ciclePath, false)
        val floatArray = FloatArray(2)
        val posTan = pathMeasure.getPosTan(0f, floatArray, null)//拿到手柄的终点
        searchPath.lineTo(floatArray[0], floatArray[1])//为内圆添加手柄路径形成放大镜
    }

    override fun onDraw(canvas: Canvas?) {
        canvas?.translate(mWidth / 2, mHeight / 2)//移动坐标到组件中心
        canvas?.drawPath(searchPath, mPaint)
        canvas?.drawPath(ciclePath,mPaint)
    }
}
android自定义view--SearchView,第3张
静态图

为静态图添加动态效果

package com.hzb.myutils.view

import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import com.hzb.utilsbox.utils.LogUtil


class MySearchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    constructor(context: Context?) : this(context, null)

    private val mPaint = Paint()//画笔
    private var offestFactoty = 0.9f //偏移因子画布是组件的0.9
    private var mWidth: Float = 0.0f  //组件宽度
    private var mHeight: Float = 0.0f //组件高度
    private lateinit var searchRecf: RectF
    private lateinit var cicleRecf: RectF
    private val searchPath: Path = Path() //放大镜的path
    private val ciclePath: Path = Path()  //外圆的path
    private var viewStatus = AnimStatus.NONE
    private var isTopAnim:Boolean=false
    private var animatorValue: Float = 0.0f  //动画变量,0-1
    //属性动画
    private lateinit var startAnim: ValueAnimator
    private lateinit var searingAnim: ValueAnimator
    private lateinit var endAnim: ValueAnimator
  
    private enum class AnimStatus { //标志动画状态
        NONE, START, SEARING, END//初始状态,开始搜索,搜索中,结束搜索
    }


    init {
        initPaint()
        initAnimator()
        initEvent()
    }

    private fun initAnimator() {
        //AnimStatus.START状态的动画
        startAnim = ValueAnimator.ofFloat(0f, 1f)
        startAnim.duration = 1000
        startAnim.addUpdateListener { animation ->
            animatorValue = animation.animatedValue as Float
            invalidate()
        }
        //AniStatus.SEARING状态动画
        searingAnim = ValueAnimator.ofFloat(0f, 1f)
        searingAnim.interpolator = LinearInterpolator()
        searingAnim.duration=1500
        searingAnim.repeatCount=ValueAnimator.INFINITE
        searingAnim.repeatMode=ValueAnimator.RESTART
        searingAnim.addUpdateListener { animation ->
            if (viewStatus==AnimStatus.SEARING) { //这里必须添加,不然放大镜会有一刹那全部显示
                animatorValue = animation.animatedValue as Float
                invalidate()
            }
        }
        //AniStatus.END状态动画
        endAnim = ValueAnimator.ofFloat(1f, 0f)
        endAnim.duration=1000
        endAnim.addUpdateListener { animation ->
            animatorValue = animation.animatedValue as Float
            invalidate()
        }

    }
  
    private fun initPaint() {
        mPaint.color = Color.BLUE
        mPaint.isAntiAlias = true
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeCap = Paint.Cap.ROUND
        mPaint.strokeWidth = 8f
    }

    1、onSizeChang()中得到组件大小
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w.toFloat()
        mHeight = h.toFloat()
        initPath()
    }

    //2、填充Path的放大镜和外圆
    private fun initPath() {
        cicleRecf = RectF(-mWidth / 2 * offestFactoty, -mHeight / 2 * offestFactoty, mWidth / 2 * offestFactoty, mHeight / 2 * offestFactoty)
        searchRecf = RectF(-mWidth / 5, -mHeight / 5, mWidth / 5, mHeight / 5)
        searchPath.addArc(searchRecf, 45f, 359.9f)
        ciclePath.addArc(cicleRecf, 45f, 359.9f)
        val pathMeasure = PathMeasure(ciclePath, false)
        val floatArray = FloatArray(2)
        val posTan = pathMeasure.getPosTan(0f, floatArray, null)
        searchPath.lineTo(floatArray[0], floatArray[1])//放大镜的手柄
    }

    override fun onDraw(canvas: Canvas?) {
        canvas?.translate(mWidth / 2, mHeight / 2)//移动坐标到组件中心
        when (viewStatus) {
            AnimStatus.NONE -> {  //初识转态
                canvas?.drawPath(searchPath, mPaint)
            }
            AnimStatus.START -> {  //开始搜索
                val pathMeasure = PathMeasure(searchPath, false)
                val dst = Path()
                pathMeasure.getSegment(pathMeasure.length * animatorValue, pathMeasure.length, dst, true)
                canvas?.drawPath(dst, mPaint)
            }
            AnimStatus.SEARING -> { //搜索中
                val pathMeasure = PathMeasure(ciclePath, false)
                val dst = Path()
                val stop = pathMeasure.length * animatorValue
                val start = (stop - (0.5 - Math.abs(animatorValue - 0.5)) * pathMeasure.length/2).toFloat()
                pathMeasure.getSegment(start,stop, dst, true)
                canvas?.drawPath(dst, mPaint)
            }
            AnimStatus.END -> {   //搜索结束
                val pathMeasure = PathMeasure(searchPath, false)
                val dst = Path()
                pathMeasure.getSegment(pathMeasure.length*animatorValue , pathMeasure.length,  dst, true)
                canvas?.drawPath(dst, mPaint)
            }
        }
    }

    /**
     * 开始动画
     */
    fun startAnim() {
        viewStatus=AnimStatus.START
        startAnim.start()
        this.isClickable = false
    }

    /**
     * 结束动画
     */
    fun stopAnim(){
        isTopAnim=true
    }
    /**
     * 监听动画结束
     */
    private fun initEvent() {

        val listener: Animator.AnimatorListener = object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {
                if (isTopAnim) {
                    animation?.cancel()
                    viewStatus=AnimStatus.END
                    endAnim.start()
                }
            }

            override fun onAnimationEnd(animation: Animator?) {
                val name = Thread.currentThread().name
                LogUtil.i(name)
                when (viewStatus) {
                    AnimStatus.START -> {
                        searingAnim.start()
                        viewStatus=AnimStatus.SEARING
                    }

                    AnimStatus.END -> {
                        viewStatus = AnimStatus.NONE
                        this@MySearchView.isClickable = true
                        isTopAnim=false
                    }
                }
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }

        }

        startAnim.addListener(listener)
        searingAnim.addListener(listener)
        endAnim.addListener(listener)
    }
}

运行效果


android自定义view--SearchView,第4张
效果gif

android自定义view--SearchView,第5张
默认文件1631847282023.png

https://www.xamrdz.com/lan/5rs2016558.html

相关文章: