View的属性动画
在许多的场景中,我们可能不只需要简单的移动,旋转,透明,变大缩小等等,我们需要加入持续的时间,定格的时间,多种组合特效等等,于是我们需要使用 Android 的动画能力。在早期的 Android 中,只有帧动画和 View 动画。View 动画提供了 AlphaAnimation、 TranslateAnimation、 RotateAnimation 和ScaleAnimation 这4种动画方式。但他们不具有交互性,即当某个元素发生 View 动画之后,其响应事件的位置依然在动画进行前的地方,所以 View 动画只能做普通的动画效果,要避免交互操作。于是我们使用 ObjectAnimator 进行更精细化的控制,控制一个对象和一个属性值,使用多个 ObjectAnimator 组合到 AnimatorSet 形成一个动画。
ValueAnimator
ObjectAnimator 是继承了 ValueAnimator, 而 ValueAnimator 是不提供任何动画效果,它只是一个数值发生器,用来产生有一定规律的数字从而让调用者控制动画的实现过程。
ValueAnimator 可以通过代码或者资源文件创建。以下是它的资源文件实例:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse"/>
从 API23 开始,还可以使用 PropertyValuesHolder 和 Keyframe 资源标签的组合来创建多步动画。
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatCount="1"
android:repeatMode="reverse">
<propertyValuesHolder>
<keyframe android:fraction="0" android:value="1"/>
<keyframe android:fraction=".2" android:value=".4"/>
<keyframe android:fraction="1" android:value="0"/>
</propertyValuesHolder>
</animator>
注意:可以为每个关键帧指定明确的分数值(从 0 到 1),以确定动画在整个持续时间内何时到达该值。
也可以在代码中创建一个 ValueAnimator。 ValueAnimator 提供了一个 AnimatorUpdateListener() 监听数值的变化。
val valueAnimator = ValueAnimator.ofFloat(0F, 100F)
valueAnimator.setTarget(it)
valueAnimator.duration = 1000
valueAnimator.start()
valueAnimator.addUpdateListener { animation ->
val mFloat = animation?.animatedValue
println(mFloat)
}
ObjectAnimator
创建一个 ObjectAnimator 只需通过其静态工厂类直接返还一个 ObjectAnimator 对象。参数包括一个对象和对象的属性名字,但是这个属性必须有 get 和 set 方法。下面是旋转动画的示例:
val objectAnimator = ObjectAnimator.ofFloat(it, "rotationX", 180.toFloat())
objectAnimator.duration = 1000
objectAnimator.start()
查看 ObjectAnimator 的 ofFloat(),第一个参数是要操作的 Object, 第二个参数是要操作的属性,最后一个参数是一个可变的 float 类型的可变数组,这里设置了旋转到180度。
注意:这边是每次都旋转到180度,如果是要每次点击旋转180度,需要将第三个参数修改成(it.rotationX + 180).toFloat()
/**
* Constructs and returns an ObjectAnimator that animates between float values. A single
* value implies that that value is the one being animated to, in which case the start value
* will be derived from the property being animated and the target object when {@link #start()}
* is called for the first time. Two values imply starting and ending values. More than two
* values imply a starting value, values to animate through along the way, and an ending value
* (these values will be distributed evenly across the duration of the animation).
*
* @param target The object whose property is to be animated. This object should
* have a public method on it called <code>setName()</code>, where <code>name</code> is
* the value of the <code>propertyName</code> parameter.
* @param propertyName The name of the property being animated.
* @param values A set of values that the animation will animate between over time.
* @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
注意:在使用ObjectAnimator的时候,要操作的属性必须要有get和set方法,不然ObjectAnimator就无法生效。如果一个属性没有get、set方法,也可以通过自定义一个属性类或包装类来间接的给这个属性增加get和set方法。
一个完整的动画,会具有 Start、 Repeat、 End、 Cancel 这4个过程,可以分别对四个过程进行监听。
objectAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
Log.i(TAG, "start")
}
override fun onAnimationEnd(animation: Animator?) {
Log.i(TAG, "end")
}
override fun onAnimationCancel(animation: Animator?) {
Log.i(TAG, "cancel")
}
override fun onAnimationRepeat(animation: Animator?) {
Log.i(TAG, "repeat")
}
})
但是绝大多数的时候,我们只关心 onAnimationEnd 事件, Android 也提供了 AnimatorListenerAdapter 来让我们选择必要的事件进行监听。
objectAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
}
})
AnimatorSet
上面讲到,一个 ObjectAnimator 对应了一个动画,有些时候,我们需要多个动画组合,例如先左移,再旋转,再放大等等。这就需要一个将这些动画组合起来的容器。Android 也为我们提供了一个 AnimatorSet 类来完成组合动画的功能。AninmatorSet 类提供了一个 play() 方法,我们向这个方法中传入一个 Animator 对象( ValueAnimator 或 ObjectAnimator ),将会返回一个 AnimatorSet.Builder 的实例。通过 play() 方法初始化的 Builder 可以往后面插入连续的 Animator 并且决定它们的排列顺序。
public class Builder {
/**
* This tracks the current node being processed. It is supplied to the play() method
* of AnimatorSet and passed into the constructor of Builder.
*/
private Node mCurrentNode;
/**
* package-private constructor. Builders are only constructed by AnimatorSet, when the
* play() method is called.
*
* @param anim The animation that is the dependency for the other animations passed into
* the other methods of this Builder object.
*/
Builder(Animator anim) {
mDependencyDirty = true;
mCurrentNode = getNodeForAnimation(anim);
}
/**
* Sets up the given animation to play at the same time as the animation supplied in the
* {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
*
* @param anim The animation that will play when the animation supplied to the
* {@link AnimatorSet#play(Animator)} method starts.
*/
public Builder with(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addSibling(node);
return this;
}
/**
* Sets up the given animation to play when the animation supplied in the
* {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
* ends.
*
* @param anim The animation that will play when the animation supplied to the
* {@link AnimatorSet#play(Animator)} method ends.
*/
public Builder before(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addChild(node);
return this;
}
/**
* Sets up the given animation to play when the animation supplied in the
* {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
* to start when the animation supplied in this method call ends.
*
* @param anim The animation whose end will cause the animation supplied to the
* {@link AnimatorSet#play(Animator)} method to play.
*/
public Builder after(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addParent(node);
return this;
}
/**
* Sets up the animation supplied in the
* {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
* to play when the given amount of time elapses.
*
* @param delay The number of milliseconds that should elapse before the
* animation starts.
*/
public Builder after(long delay) {
// setup dummy ValueAnimator just to run the clock
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(delay);
after(anim);
return this;
}
}
通过查看 AnimatorSet 的 Builder 内部类的源码,可以看到它主要提供了4个方法来控制动画顺序。
- with(Animator anim):将现有的动画和传入的动画同时执行
- before(Animator anim):将现有动画插入到传入的动画之前执行
- after(Animator anim):将现有动画插入到传入的动画之后执行
- after(long delay):将现有动画延迟指定毫秒后执行
AnimatorSet 用法如下:
val objectAnimator1 = ObjectAnimator.ofFloat(it, "rotationX", it.rotationX + 180)
val objectAnimator2 = ObjectAnimator.ofFloat(it, "translationX", it.translationX + 100)
val objectAnimator3 = ObjectAnimator.ofFloat(it, "scaleX", it.scaleX + 1)
val set = AnimatorSet()
set.duration = 1000
set.play(objectAnimator1).with(objectAnimator2).after(objectAnimator3)
set.start()
PropertyValuesHolder
除了 AnimatorSet 类,还可以使用 PropertyValuesHolder 类来实现组合动画。不过使用 PropertyValuesHolder 只能是多个动画一起执行。我们使用 ObjectAnimator.ofPropertyValuesHolder(Object target, PropertyValuesHolder… values) 方法,其中第一个参数是动画的目标对象,之后的参数是 PropertyValuesHolder 类的实例。
val propertyValuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", it.scaleX + 1)
val propertyValuesHolder2 = PropertyValuesHolder.ofFloat("rotationX", it.rotationX + 180)
val propertyValuesHolder3 = PropertyValuesHolder.ofFloat("translationX", it.translationX + 100)
val objectAnimator = ObjectAnimator.ofPropertyValuesHolder(it, propertyValuesHolder1, propertyValuesHolder2, propertyValuesHolder3)
objectAnimator.duration = 2000
objectAnimator.start()
《Android进阶之光》刘望舒 电子工业出版社