自定义View学习

import android.animation.Animator
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.os.Handler
import android.os.Message
import android.util.AttributeSet
import android.view.View
import com.cninct.common.util.AppLog
import kotlin.math.abs



@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
class SearchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    // 画笔
    private var mPaint: Paint? = null

    // View 宽高
    private var mViewWidth = 0
    private var mViewHeight = 0

    constructor(context: Context?) : this(context, null)

    private fun initAll() {
        initPaint()
        initPath()
        initListener()
        initHandler()
        initAnimator()

        // 进入开始动画
        mCurrentState = State.STARTING
        mStartingAnimator!!.start()
    }

    // 这个视图拥有的状态
    enum class State {
        NONE, STARTING, SEARCHING, ENDING
    }

    // 当前的状态(非常重要)
    private var mCurrentState = State.NONE

    // 放大镜与外部圆环
    private var pathSrarch: Path? = null
    private var pathCircle: Path? = null

    // 测量Path 并截取部分的工具
    private var mMeasure: PathMeasure? = null

    // 默认的动效周期 2s
    private val defaultDuration = 2000

    // 控制各个过程的动画
    private var mStartingAnimator: ValueAnimator? = null
    private var mSearchingAnimator: ValueAnimator? = null
    private var mEndingAnimator: ValueAnimator? = null

    // 动画数值(用于控制动画状态,因为同一时间内只允许有一种状态出现,具体数值处理取决于当前状态)
    private var mAnimatorValue = 0f

    // 动效过程监听器
    private var mUpdateListener: AnimatorUpdateListener? = null
    private var mAnimatorListener: Animator.AnimatorListener? = null

    // 用于控制动画状态转换
    private var mAnimatorHandler: Handler? = null

    // 判断是否已经搜索结束
    private var isOver = false
    private var count = 0
    private fun initPaint() {
        mPaint = Paint()
        mPaint?.style = Paint.Style.STROKE
        mPaint?.color = Color.WHITE
        mPaint?.strokeWidth = 15f
        mPaint?.strokeCap = Paint.Cap.ROUND
        mPaint?.isAntiAlias = true
    }

    private fun initPath() {
        pathSrarch = Path()
        pathCircle = Path()
        mMeasure = PathMeasure()

        // 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值
        val oval1 = RectF(-50f, -50f, 50f, 50f) // 放大镜圆环
        pathSrarch?.addArc(oval1, 45f, 359.9f)
        val oval2 = RectF(-100f, -100f, 100f, 100f) // 外部圆环
        pathCircle?.addArc(oval2, 45f, -359.9f)
        val pos = FloatArray(2)
        mMeasure!!.setPath(pathCircle, false) // 放大镜把手的位置
        mMeasure!!.getPosTan(0f, pos, null)
        pathSrarch?.lineTo(pos[0], pos[1]) // 放大镜把手
        AppLog.e("pos:${pos[0]}、${pos[1]}")
    }

    private fun initListener() {
        mUpdateListener = AnimatorUpdateListener { animation ->
            mAnimatorValue = animation.animatedValue as Float
            invalidate()
        }
        mAnimatorListener = object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator?) {}
            override fun onAnimationEnd(animation: Animator?) {
                // getHandle发消息通知动画状态更新
                mAnimatorHandler?.sendEmptyMessage(0)
            }

            override fun onAnimationCancel(animation: Animator?) {}
            override fun onAnimationRepeat(animation: Animator?) {}
        }
    }

    private fun initHandler() {
        mAnimatorHandler = @SuppressLint("HandlerLeak")
        object : Handler() {
            override fun handleMessage(msg: Message?) {
                super.handleMessage(msg)
                when (mCurrentState) {
                    State.STARTING -> {
                        // 从开始动画转换好搜索动画
                        isOver = false
                        mCurrentState = State.SEARCHING
                        mStartingAnimator!!.removeAllListeners()
                        mSearchingAnimator!!.start()
                    }
                    State.SEARCHING -> if (!isOver) {  // 如果搜索未结束 则继续执行搜索动画
                        mSearchingAnimator!!.start()
                        AppLog.e("Update:RESTART")
                        count++
                        if (count > 2) {       // count大于2则进入结束状态
                            isOver = true
                        }
                    } else {        // 如果搜索已经结束 则进入结束动画
                        mCurrentState = State.ENDING
                        mEndingAnimator!!.start()
                    }
                    State.ENDING ->                         // 从结束动画转变为无状态
                        mCurrentState = State.NONE
                    else -> {

                    }
                }
            }

        }
    }

    private fun initAnimator() {
        mStartingAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(defaultDuration.toLong())
        mSearchingAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(defaultDuration.toLong())
        mEndingAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(defaultDuration.toLong())
        mStartingAnimator?.addUpdateListener(mUpdateListener)
        mSearchingAnimator?.addUpdateListener(mUpdateListener)
        mEndingAnimator?.addUpdateListener(mUpdateListener)
        mStartingAnimator?.addListener(mAnimatorListener)
        mSearchingAnimator?.addListener(mAnimatorListener)
        mEndingAnimator?.addListener(mAnimatorListener)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mViewWidth = w
        mViewHeight = h
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawSearch(canvas)
    }

    private fun drawSearch(canvas: Canvas) {
        mPaint?.color = Color.WHITE
        canvas.translate(mViewWidth / 2f, mViewHeight / 2f)
        canvas.drawColor(Color.parseColor("#0082D7"))
        when (mCurrentState) {
            State.NONE -> canvas.drawPath(pathSrarch!!, mPaint!!)
            State.STARTING -> {
                mMeasure!!.setPath(pathSrarch, false)
                val dst = Path()
                mMeasure!!.getSegment(
                    mMeasure!!.length * mAnimatorValue,
                    mMeasure!!.length,
                    dst,
                    true
                )
                canvas.drawPath(dst, mPaint!!)
            }
            State.SEARCHING -> {
                mMeasure!!.setPath(pathCircle, false)
                val dst2 = Path()
                val stop = mMeasure!!.length * mAnimatorValue
                val start = (stop - (0.5 - abs(mAnimatorValue - 0.5)) * 200f).toFloat()
                mMeasure!!.getSegment(start, stop, dst2, true)
                canvas.drawPath(dst2, mPaint!!)
            }
            State.ENDING -> {
                mMeasure!!.setPath(pathSrarch, false)
                val dst3 = Path()
                mMeasure!!.getSegment(
                    mMeasure!!.length * mAnimatorValue,
                    mMeasure!!.length,
                    dst3,
                    true
                )
                canvas.drawPath(dst3, mPaint!!)
            }
        }
    }

    init {
        initAll()
    }
}
初始状态初始状态,没有任何动效,只显示一个搜索标志 🔍
准备搜索放大镜图标逐渐变化为一个点
正在搜索围绕这一个圆环运动,并且线段长度会周期性变化
准备结束从一个点逐渐变化成为放大镜图标

效果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值