Android代码实现新年贺卡动画示例详解
dora 人气:0引言
什么?兔了个兔?吐了还要吐?首先今天,我们自己用android程序实现一个兔年的新年贺卡。下面就是见证美好的时刻,上效果。
好,我们来使用Android动画的知识,来实现这样一个动画效果吧。
需要使用到的知识点
架构设计、Android视图动画、TypeEvaluator、Path、组合模式、代理模式。
思路分析
我们回顾动画的种类,补间动画、帧动画、属性动画以及Android View自带的视图动画。我们今天自己基于属性动画来打造一个山寨版的Android视图动画吧。我们可以从平移动画、缩放动画、旋转动画和透明度动画中抽象出一个基类Action类。我是不会告诉你这个类的命名我是抄的cocos2d的。然后我们扩展Action类,实现这四种动画,再作用在View上。这样就可以让View按我们的动画框架播放动画了。
代码实现
/** * 组合的action可以直接交给view执行。 */ interface Action<A : Action<A>> { fun add(action: A): A fun getAnimator(): Animator<A> fun startAnimation(view: View, duration: Long) }
抽象一个Action接口,Action还可以添加Action,这里是组合模式的结构。
import android.view.View import dora.widget.animator.AlphaAnimator import dora.widget.animator.Animator class AlphaAction(val alpha: Float) : Action<AlphaAction> { private var animator = AlphaAnimator() override fun add(action: AlphaAction): AlphaAction { animator.add(action) return this } override fun startAnimation(view: View, duration: Long) { animator.startAnimation(view, duration) } override fun getAnimator(): Animator<AlphaAction> { return animator } operator fun plus(action: AlphaAction) = add(action) init { animator.add(this) } }
我们以透明度动画为例,在Animator中实现属性动画的逻辑,然后聚合到Action类的实现,通过代理的方式调用我们的动画实现。这里我们重写了+号操作符,这样可以支持两个对象进行相加,这个是Kotlin模仿C++的语法。
import android.view.View import dora.widget.action.Action import java.util.* abstract class Animator<A : Action<A>>: Action<A> { protected lateinit var targetView: View protected var actionTree: MutableList<A> = ArrayList() override fun add(action: A): A { actionTree.add(action) return actionTree[actionTree.size - 1] } override fun startAnimation(view: View, duration: Long) { targetView = view } override fun getAnimator(): Animator<A> { return this } }
在Animator中,将所有的Action放到一个List集合中保存起来,当我们调用startAnimation()方法,则可以将传入的View拿到,并执行动画。
class AlphaAnimator : Animator<AlphaAction>() { override fun startAnimation(view: View, duration: Long) { super.startAnimation(view, duration) actionTree.add(0, AlphaAction(1.0f)) val animator = ObjectAnimator.ofObject( this, ALPHA, AlphaEvaluator(), *actionTree.toTypedArray() ) animator.duration = duration animator.start() } fun setAlpha(action: AlphaAction) { val alpha = action.alpha targetView.alpha = alpha } private class AlphaEvaluator : TypeEvaluator<AlphaAction> { override fun evaluate( fraction: Float, startValue: AlphaAction, endValue: AlphaAction ): AlphaAction { val action: AlphaAction val startAlpha = startValue.alpha val endAlpha = endValue.alpha action = if (endAlpha > startAlpha) { AlphaAction(startAlpha + fraction * (endAlpha - startAlpha)) } else { AlphaAction(startAlpha - fraction * (startAlpha - endAlpha)) } return action } } companion object { private const val ALPHA = "alpha" } override fun getAnimator(): Animator<AlphaAction> { return this } }
比如AlphaAnimator的实现,我们这里最关键的一行代码就是使用了ObjectAnimator,用它来监听该对象属性的变化。比如这里我们监听alpha属性实际上是监听的setAlpha方法。动画变化的中间值则是通过TypeEvaluator估值器来进行计算估值的。在startAnimation()方法被调用的时候,我们默认在最前面添加了一个默认值。
actionTree.add(0, AlphaAction(1.0f))
我这里只是抛砖引玉,你可以做得更好,比如将初始状态不要写死,让子类去指定或在使用的时候动态指定,这样就会更加的灵活。
abstract class PathAction internal constructor( val x: Float, val y: Float ) : Action<PathAction> { private var animator = PathAnimator() override fun add(action: PathAction): PathAction { animator.add(action) return this } override fun startAnimation(view: View, duration: Long) { animator.startAnimation(view, duration) } override fun getAnimator(): Animator<PathAction> { return animator } operator fun plus(action: PathAction) = add(action) init { animator.add(this) } }
移动的动画也是类似的逻辑,我们基于Path实现移动动画。
class PathAnimator : Animator<PathAction>() { private val PATH = "path" override fun startAnimation(view: View, duration: Long) { super.startAnimation(view, duration) actionTree.add(0, MoveTo(0f, 0f)) val animator = ObjectAnimator.ofObject( this, PATH, PathEvaluator(), *actionTree.toTypedArray() ) animator.duration = duration animator.start() } fun setPath(action: MoveTo) { val x = action.x val y = action.y targetView.translationX = x targetView.translationY = y } private inner class PathEvaluator : TypeEvaluator<PathAction> { override fun evaluate(fraction: Float, startValue: PathAction, endValue: PathAction): PathAction { var x = 0f var y = 0f if (endValue is MoveTo) { x = endValue.x y = endValue.y } if (endValue is LineTo) { x = startValue.x + fraction * (endValue.x - startValue.x) y = startValue.y + fraction * (endValue.y - startValue.y) } val ratio = 1 - fraction if (endValue is QuadTo) { x = Math.pow(ratio.toDouble(), 2.0) .toFloat() * startValue.x + (2 * fraction * ratio * (endValue).inflectionX) + (Math.pow( endValue.x.toDouble(), 2.0 ) .toFloat() * Math.pow(fraction.toDouble(), 2.0).toFloat()) y = Math.pow(ratio.toDouble(), 2.0) .toFloat() * startValue.y + (2 * fraction * ratio * (endValue).inflectionY) + (Math.pow( endValue.y.toDouble(), 2.0 ) .toFloat() * Math.pow(fraction.toDouble(), 2.0).toFloat()) } if (endValue is CubicTo) { x = Math.pow(ratio.toDouble(), 3.0).toFloat() * startValue.x + (3 * Math.pow( ratio.toDouble(), 2.0 ).toFloat() * fraction * (endValue).inflectionX1) + (3 * ratio * Math.pow(fraction.toDouble(), 2.0).toFloat() * (endValue).inflectionX2) + Math.pow(fraction.toDouble(), 3.0) .toFloat() * endValue.x y = Math.pow(ratio.toDouble(), 3.0).toFloat() * startValue.y + (3 * Math.pow( ratio.toDouble(), 2.0 ).toFloat() * fraction * (endValue).inflectionY1) + (3 * ratio * Math.pow(fraction.toDouble(), 2.0).toFloat() * (endValue).inflectionY2) + Math.pow(fraction.toDouble(), 3.0) .toFloat() * endValue.y } return MoveTo(x, y) } } override fun getAnimator(): Animator<PathAction> { return this } }
曲线运动则牵扯到一些贝瑟尔曲线的知识。 比如二阶的贝瑟尔曲线
class QuadTo(val inflectionX: Float, val inflectionY: Float, x: Float, y: Float) : PathAction(x, y)
和三阶的贝瑟尔曲线
class CubicTo( val inflectionX1: Float, val inflectionX2: Float, val inflectionY1: Float, val inflectionY2: Float, x: Float, y: Float ) : PathAction(x, y)
直线运动则是定义了MoveTo和LineTo两个类。
class MoveTo(x: Float, y: Float) : PathAction(x, y)
class LineTo(x: Float, y: Float) : PathAction(x, y)
调用动画框架API
我们贺卡的动画就是使用了以下的写法,同一类Action可以通过+号操作符进行合并,我们可以同时调用这四类Action进行动画效果的叠加,这样可以让动画效果更加丰富。
(AlphaAction(0.2f) + AlphaAction(1f)).startAnimation(ivRabbit, 2000) (MoveTo(-500f, 100f) + LineTo(-400f, 80f) + LineTo(-300f, 50f) + LineTo(-200f, 100f) + LineTo(-100f, 80f) + LineTo(0f, 100f) + LineTo(100f, 80f) + LineTo(200f, 50f) + LineTo(300f, 100f) + LineTo(400f, 80f) ) .startAnimation(ivRabbit, 2000) (RotateAction(0f) + RotateAction(180f)+ RotateAction(360f)) .startAnimation(ivRabbit, 4000) ScaleAction(2f, 2f).startAnimation(ivRabbit, 8000) Handler().postDelayed({ MoveTo(0f, 0f).startAnimation(ivRabbit, 500) }, 8000)
兴趣是最好的老师,本文篇幅有限,我们可以通过Android的代码在Android手机上实现各种各样炫酷的效果。跟着哆啦一起玩转Android自定义View吧。
加载全部内容