前言
抖音作为一个国民级的娱乐短视频应用,小编平常的时候也没少刷,往往一刷一关,半天就过去了。作为一个 Android 开发者,玩自家的应用没有玩抖音的时间长,感觉挺丧的。出于一种莫名其妙的心理,就想着从抖音那里偷学一些东西。但是从哪里开始那?想了想,还是先偷一个刷新动画吧。
观察&思考
先是觉得抖音的刷新动画挺好玩的,然后仔细观察了下。首先它有两个圆,一个红的,一个白的,感官上两个圆像是围着一个大圆在做圆周运动,忽左忽右,忽前忽后的,说明它用到了平移动画。然后两个球的大小也是在先小后大,说明它在做缩放动画。最后,两个球总是互相换来换去,说明它有一定的周期性,且相交的区域用黑色填充。好了,我们观察的情况大致符合咱们上面的描述。那么,你有实现它的思路了吗?不妨用笔画一画,写一写。
代码实现
// 假设有一个圆,沿着该圆心在水平方向上画一条线,然后以该线与圆的两个交点作为圆心画两圆,半径为圆的半径,
// 最后,两个圆沿着该线做平移和缩放动画,两圆相交的地方用黑色填充,直到两圆的圆心互换。
// 此时,走了一个周期,然后再沿着反方向走一遍。
// 注意: 圆心一直在切线上移动。
class DouYinRefresh : Drawable{
private val redPaint = Paint()
private val whitePaint = Paint()
private val blackPaint = Paint()
private val redPath = Path() // 红球轨迹
private val whitePath = Path() // 白球轨迹
private val blackPath = Path() // 中间相交的黑色轨迹
// 假想圆的圆心和半径
private var centerX: Float = 0f
private var centerY: Float = 0f
private var radius: Float = 30f
private var offset: Float = 0f
private var scale: Float = 0f
// 记录两个圆交换次数
private var cycleCount: Int = 0
// 动画集合
private val animatorSet: AnimatorSet = AnimatorSet()
constructor() {
redPaint.color = Color.RED
redPaint.isAntiAlias = true
redPaint.style = Paint.Style.FILL
whitePaint.color = Color.WHITE
whitePaint.isAntiAlias = true
whitePaint.style = Paint.Style.FILL
blackPaint.color = Color.BLACK
blackPaint.isAntiAlias = true
blackPaint.style = Paint.Style.FILL
}
override fun draw(canvas: Canvas) {
// 每交换一次,两个圆移动的方向改变
val redDirection = if (cycleCount and 1 == 0) 1 else -1
val whiteDirection = if (cycleCount and 1 == 0) -1 else 1
redPath.reset()
redPath.addCircle(
centerX - radius + redDirection * offset,
centerY,
radius,
Path.Direction.CW
)
canvas.drawPath(redPath, redPaint)
whitePath.reset()
whitePath.addCircle(
centerX + radius + whiteDirection * offset,
centerY,
radius * scale,
Path.Direction.CW
)
canvas.drawPath(whitePath, whitePaint)
blackPath.reset()
blackPath.op(redPath, whitePath, Path.Op.INTERSECT)
canvas.drawPath(blackPath, blackPaint)
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
centerX = bounds.width() / 2f
centerY = bounds.height() / 2f
}
override fun setAlpha(alpha: Int) {
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
@SuppressLint("WrongConstant")
override fun getOpacity(): Int {
return PixelFormat.RGBA_8888
}
fun stopAnimation() {
if (animatorSet.isRunning) {
animatorSet.end()
}
}
fun isRunning(): Boolean {
return animatorSet.isRunning
}
fun startAnimation() {
val translateAnimator = ValueAnimator.ofFloat(0f, 2 * radius)
translateAnimator.repeatCount = -1
translateAnimator.startDelay = 60
translateAnimator.duration = 500
translateAnimator.interpolator = AccelerateDecelerateInterpolator()
translateAnimator.addUpdateListener {
offset = it.animatedValue as Float
invalidateSelf()
}
val scaleAnimator = ValueAnimator.ofFloat(1f, 0.5f, 1f)
scaleAnimator.startDelay = 60
scaleAnimator.repeatCount = -1
scaleAnimator.duration = 500
scaleAnimator.interpolator = AccelerateDecelerateInterpolator()
scaleAnimator.addUpdateListener {
scale = it.animatedValue as Float
invalidateSelf()
}
translateAnimator.addListener(onEnd = {
cycleCount++
offset = 0f
})
animatorSet.playTogether(translateAnimator, scaleAnimator)
animatorSet.start()
}
}
思路想明白了,剩下的就交给系统提供的 API 去实现了。
使用
val douyin = DouYinRefresh()
findViewById<ImageView>(R.id.loading).setImageDrawable(douyin)
douyin.startAnimation()
总结
当我们遇到比较新奇的东西且你有非常冲动的想把握住它,停下手中的事情,画10分钟,为自己加一个 todo 。按照事情的重要与紧急程度,重新分一下时间象限图。在之后的时间内,努力的把握住它。