Property Animation是谷歌在Android3.0的时候引入的,这个动画改变的是对象的实际属性,在视图动画(View Animation)中,改变的只是视图绘制的位置,而不是实际视图本身。
Property Animation允许我们定义动画的下列特征
Duration:动画的持续时间,默认为300ms。
Time interpolation:时间插值,如何根据动画当前经过的时间计算属性值。
Repeat count and behavior:重复的次数和行为,指定动画在持续时间结束时是否重复播放,以及重复播放的方式,如顺序播放还是倒序播放。
Animator sets:动画的集合,这些动画集合可以一起播放,顺序播放或在指定延迟后播放。
Frame refresh delay:帧的刷新时间,默认为10ms。最终取决于硬件环境和当前系统状态。
Property Animation是如何工作的?
上图表示了一个线性插值的动画实例。它用x属性表示在屏幕上的水平位置,动画的持续时间设置为40ms,移动距离为40像素。每10ms,即默认的刷新速率,对象水平移动10个像素。在40ms的末尾,动画停止。并且动画停留在水平40像素的位置。
ValueAnimator
ValueAnimator 即表示一个动画,包含了要动画的属性的起始值和结束值,以及动画的持续时间。
ValueAnimator 封装了一个TimeInterpolator,它定义了动画的插值方法。ValueAnimator还封装了一个TypeEvaluator,它定义了如何计算正在动画的属性的值。在整个动画过程中,ValueAnimator会根据动画的持续时间和已经过去的时间计算出一个时间因子。如上图中t=10ms时,时间因子为10ms / 40ms = 0.25。在计算出时间因子后会调用TimeInterpolator的值计算出插值因子。上图中插值因子总是与时间因子相同。在计算出插值因子后,ValueAnimator会调用适当的TypeEvaluator,根据动画的插值因子,动画的起始值和结束值计算出正在动画的属性值,在上图中t=10ms时插值因子为0.25,因此此时的改属性值为0.25 * (40-0) = 10。
可以通过ValueAnimator.ofXXX的静态方法来创建一个ValueAniamtor对象,这些ValueAnimator分别对不同的数据类型来进行动画,包括下面5种:
ofInt(),ofFloat(),ofArgb(),ofObject(),ofPropertyValuesHolder()。它们分别为Int类型的数据,Float类型的数据,颜色类型十六进制数据,自定义类型的数据,PropertyValuesHolder类型的数据进行动画。
一个属性动画包括两部分,计算动画值和正在动画的对象和属性上设置这些值。ValueAnimator只完成了第一部分,想要完成第二部分,必须实现Animator.AnimatorUpdateListener接口。
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 1000
addUpdateListener { animation ->
Float animatedValue = animation.animatedValue as Float
Log.d("TAG", "currentValue is $animatedValue");
}
start()
}
上面中代码只是在Log中输入了一些信息,可以改为自己想做的工作。在ofFloat()方法中可以传入多个参数。这些参数的时间间隔均匀分布。下面的代码中表示在1秒钟内从0到1,再到5,再到10。我们还可以调用setStartDelay()设置延时播放的时间,调用setRepeatCount()和setRepeatMode()设置动画重复播放的次数和重复播放的模式。0代表不重复。模式包括ValueAnimator.RESTART和ValueAnimator.REVERSE(分别为重复播放和倒序播放)。
ValueAnimator.ofFloat(0f, 1f, 5f, 10f).apply {
duration = 1000
start()
}
ObjectAnimator
该类是ValueAnimator的子类。允许将目标对象和对象的属性设置为动画,在计算动画的新值时会相应的更新属性。即完成属性动画的两部分。实例化一个ObjectAnimator类似于ValueAnimator,但还可以指定要进行动画的对象和该对象的属性(以字符串的形式)的名称。
ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f).apply {
duration = 200
start()
}
上例中表示把imageView对象从透明变为不透明。大多数的时候我们使用ObjectAnimator来进行动画比较简单,但使用ObjectAnimator有一些限制。
1.动画中的object属性必须具有一个setter方法,形式为set<PropertyName>.比如如果属性名为foo,则必须具有一个setFoo()方法。如果values...的参数只有一个的话,它会被假定为动画的结束值。动画从对象的当前属性值到结束值。为了获得对象的当前属性值,动画对象属性必须具有一个getter方法。形式为get<PropertyName>。比如如果属性名为foo,则必须具有一个getFoo()方法。
2.要进行动画对象的属性的getter方法(如果需要的话)的返回值和setter方法的参数类型必须与ObjectAnimator指定的起始值和结束值的类型相同。
3.根据正在动画的属性或对象,可能需要调用invalidate()方法来强制控件进行重绘。
除了上面以字符串的形式指定对象的属性外,还可以使用Property对象的形式指定属性。下面的代码和上面的效果是一样的。
val property = object: Property<View, Float>(Float::class.java, "alpha") {
override fun set(view: View, value: Float) {
view.alpha = value
}
override fun get(view: View): Float {
return view.alpha
}
}
ObjectAnimator.ofFloat(imageView, property, 0f, 1f).apply {
duration = 200
start()
}
AnimatorSet
在很多时候,我们可能会希望一个动画的播放取决于另一个动画的开始或结束。Android提供了AnimatorSet来将一组动画捆绑在一起,以指定是同时播放,顺序播放还是指定的延时之后播放。该类提供了一个play()方法。我们可以传入一个Animator对象,这将会返回给我们一个AnimatorSet.Builder对象。
public Builder play(Animator anim)
该对象包含了以下方法
//将现有的动画指定延时delay毫秒后播放
public Builder after(long delay)
// 将现有的动画插入到传入的动画之后播放。也就是说先播放after()方法中的动画,在播放play()方法中的动画。
public Builder after(Animator anim)
//将现有的动画插入到传入的动画之前播放。也就是说先播放play()方法中的动画,在播放before()方法中的动画。
public Builder before(Animator anim)
//将现有的动画和传入的动画同时播放。
public Builder with(Animator anim)
AnimatorSet还提供了playTogether(Animator... items) 和 playTogether(Collection<Animator> items)来添加一组动画,该组动画为同时播放。
public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)
AnimatorSet还提供了playSequentially(Animator... items) 和 playSequentially(List<Animator> items)来添加一组动画,该组动画为顺序播放。
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)
TypeEvaluator
如果我们要进行动画的类型是Android系统未知的。我们可以实现TypeEvaluator接口来创建我们自己的类型评估器。Android系统已知的类型包括int,float,和color。他们分别对应IntEvaluator,FloatEvaluator和ArgbEvaluator。我们可查看一下FloatEvaluator的代码实现
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
可以看到,我们的FloatEvaluator实现了TypeEvaluator接口并重写了evaluate方法。在evaluate方法中会有三个参数,第一个参数代表的是动画的完成度,第二个和第三个分别是动画的开始值和结束值。
那么我们要如何去实现我们自己的类型评估器呢? 我们创建一个Point类,代码如下
data class Point(val x: Int, val y: Int)
Point类很简单,只有x,y两个变量并提供了一个构造方法,和获取x,y变量的get方法。PointEvaluator类如下
class PointEvaluator : TypeEvaluator<Point> {
override fun evaluate(fraction: Float, startValue: Point, endValue: Point): Point {
val x = (startValue.x + fraction * (endValue.x - startValue.x)).toInt()
val y = (startValue.y + fraction * (endValue.y - startValue.y)).toInt()
return Point(x, y)
}
}
我们的PointEvaluator同样实现了TypeEvaluator接口并重写了evaluate方法。根据动画完成度来计算当前动画的x,y值。并重新组合成一个新的Point对象。有了PointEvaluator。我们要对Point对象进行动画就非常简单了。
val point1 = Point(0, 0)
val point2 = Point(100, 100)
ValueAnimator.ofObject(PointEvaluator(), point1, point2).apply {
duration = 200
addUpdateListener { animation ->
val point = animation.animatedValue as Point
Log.d("TAG", "point.x = ${point.x} point.y = ${point.y}")
}
start()
}
我们先创建两个Point对象,并在构造方法中设置了x和y的值。然后通过ValueAnimator.ofObject方法来构建ValueAnimator的实例。ofObject方法需要传入一个TypeEvaluator参数,这里把我们的PointEvaluator传入就行了。
Keyframe
Keyframe被称为关键帧,它由一个时间/值对组成。它允许动画在特定时刻定义特定状态,每个关键帧也可以有自己的Interpolator来控制动画在前一关键帧的时间和这个关键帧的时间间隔之间的行为。
实例化Keyframe时,必须使用其中的一个工厂方法,ofInt(),ofFloat()或ofObject()来获取适当类型的Keyframe。然后可以通过PropertyValuesHolder的工厂方法ofKeyframe来获得PropertyValuesHolder对象。拥有该对象后可以通过传入PropertyValuesHolder对象和要动画的对象。
val key0 = Keyframe.ofFloat(0f, 0f)
val key1 = Keyframe.ofFloat(1f, 1f)
val valuesHolder = PropertyValuesHolder.ofKeyframe(
"alpha", key0, key1)
ObjectAnimator.ofPropertyValuesHolder(imageView, valuesHolder).apply {
duration = 1000
repeatCount = 1
repeatMode = ValueAnimator.REVERSE
start()
}
Animation Listeners
很多时候我们需要监听到动画的各种事件,比如动画何时开始,何时结束。Android提供了一个addListener()方法。该方法接收一个Animator.AnimatorListener。我们需要实现这个AnimatorListener就可以监听动画的各种事件
AnimationListener
onAnimationStart()-----动画开始时调用
onAnimationEnd()-----动画结束时调用
onAnimationRepeat()-----动画重复时调用
onAnimationCancel()-----动画取消时调用,取消的动画也会调用onAnimationEnd(),不管动画是怎么结束的
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
Log.d("TAG", "动画重复时调用")
}
override fun onAnimationEnd(animation: Animator?) {
Log.d("TAG", "动画结束时调用")
}
override fun onAnimationCancel(animation: Animator?) {
Log.d("TAG", "动画取消时调用")
}
override fun onAnimationStart(animation: Animator?) {
Log.d("TAG", "动画开始时调用")
}
})
当然很多时候,我们只需要监听动画的某一个事件,比如我只想监听动画的结束事件,这时候每个方法都实现一遍就会很繁琐,Android提供了AnimatorListenerAdapter,我们可以扩展它.而不必去实现AnimatorListener。
ViewPropertyAnimator
ViewPropertyAnimator提供了一种简单的方法来对View对象使用单个基础Animator动画。它的行为很像ObjectAnimator,因为它修改的是视图属性的实际值,但是在对多个属性执行动画时的效率更高。此外使用ViewPropertyAnimator也更简洁更加容易阅读。
多个动画对象
val animX = ObjectAnimator.ofFloat(imageView, "x", 50f)
val animY = ObjectAnimator.ofFloat(imageView, "y", 50f)
AnimatorSet().apply {
playTogether(animX, animY)
start()
}
一个动画对象
val valuesHolderX = PropertyValuesHolder.ofFloat("x", 50f)
val valuesHolderY = PropertyValuesHolder.ofFloat("y", 50f)
ObjectAnimator.ofPropertyValuesHolder(imageView, valuesHolderX, valuesHolderY).apply {
start()
}
采用ViewPropertyAnimator
imageView.animate().x(50f).y(100f)
使用xml文件来声明属性动画
属性动画系统允许使用xml文件来声明属性动画,而不是以代码的方式。通过XML文件来定义动画,我们可以在多个活动中来重用动画。我们所有的属性动画的xml文件应该保存到res/animtor目录下。
在xml文件中我们可以使用以下三种标签
<animator> 对应 ValueAnimator
<objectAnimator> 对应 ObjectAnimator
<set> 对应 AnimatorSet
下面的xml文件依次播放了两组动画对象。android:ordering 属性指定集合中的动画的播放顺序 sequentially 顺序播放集合中的动画,together 同时播放集合中的动画(默认).
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<set android:ordering="together">
<objectAnimator
android:propertyName="translationX"
android:duration="1000"
android:valueFrom="0"
android:valueTo="400"
android:valueType="floatType"/>
<objectAnimator
android:propertyName="translationY"
android:duration="1000"
android:valueFrom="0"
android:valueTo="300"
android:valueType="floatType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="1000"
android:valueTo="0f"/>
</set>
在代码中使用AnimatorInflater.loadAnimator()方法来进行加载。在调用setTarget()将这一动画设置到某一个对象上.
(AnimatorInflater.loadAnimator(this, R.animator.property_animator) as AnimatorSet).apply {
target = imageView
start()
}