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