android自定义view--SearchView

作者: code搬运 | 来源:发表于2018-06-26 15:19 被阅读92次

    前言

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

    searchView图片来源于网上

    思路(由简到繁)

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

    绘制静态的路径

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


    红色为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)
        }
    }
    
    静态图

    为静态图添加动态效果

    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)
        }
    }
    

    运行效果


    效果gif

    相关文章

      网友评论

        本文标题:android自定义view--SearchView

        本文链接:https://www.haomeiwen.com/subject/ahhmyftx.html