Android进阶知识(十九):属性动画及动画使用注意事项
View动画只能支持四种简单操作,除此之外View动画一个最大的缺陷:只是改变View的显示效果,并不会真正的改变View的属性。具体来说:例如屏幕左上角有一个Button,使用View动画将其移动到右下角,此刻点击右下角的Button,其绝对不会响应点击事件,而在左上角原位置则会响应。
为此,在API11的时候加入了新特性——属性动画,它对作用对象进行了扩展,属性动画可以对任何对象做动画,甚至可以没有对象。
一、属性动画使用
属性动画中有ValueAnimator、ObjectAnimator和AnimatorSet等概念,通过它们可以实现绚丽的动画。属性动画可以对任意对象的属性进行动画,动画默认时间间隔300ms,默认帧率10ms/帧。
值得一提的是,属性动画从API11才有,这严重限制了属性动画的使用,为了兼容以前的版本可以采用开源动画库nineoldandroids,但是其原理是内部通过代理View动画来实现的,因此在低版本上,其本质是View动画。
属性动画常用的动画类:ValueAnimator、ObjectAnimator(继承至ValueAnimator)和AnimatorSet。
- ValueAnimator
ValueAnimator的基础使用代码如下:
ValueAnimator valueAnim = ValueAnimator.ofFloat(0f, 1f);
valueAnim.setDuration(1000);
// 设置每一帧动画
valueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// 获取动画每一帧的值并设置每一帧动画
mImageView.setAlpha((Float) valueAnimator.getAnimatedValue());
}
});
valueAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
super.onAnimationEnd(animator);
//TODO 当动画结束的时候做操作
}
});
valueAnim.start();
上述代码实现的功能是在1000ms内,View从透明到不透明的变换过程,其中AnimatorUpdateListener用于获取动画每一帧的返回值,设置每一帧的动画;AnimatorListenerAdapter用于指定动画结束事件发生时的处理方法(关于监听器可以见后续小节)。
- ObjectAnimator
ObjectAnimator,继承ValueAnimator,允许指定target object,并且target object需要有setter方法,例如:ImageView的setRotation方法。
// 异步动画,执行起来会同时进行
ObjectAnimator.ofFloat(mImageView, "rotation", 0f, 360f)
.setDuration(1000)
.start();
ObjectAnimator.ofFloat(mImageView, "traslationX", 0f, 200f)
.setDuration(1000)
.start();
ObjectAnimator.ofFloat(mImageView, "traslationY", 0f, 200f)
.setDuration(1000)
.start();
上述三个属性动画会并行执行,原因在于属性动画是异步动画,即属性动画在调用start()方法之后是一个异步的过程,因此会同时执行三个动画。
Google为异步动画提供了更好的实现异步动画的方法:PropertyValuesHolder。例子如下:
// 实现异步动画的另一种方法
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("rotation", 0F, 360F);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("traslationX", 0F, 200F);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("traslationY", 0F, 200F);
ObjectAnimator.ofPropertyValuesHolder(mImageView, holder1, holder2, holder3)
.setDuration(1000)
.start();
PropertyValuesHolder的优点:Google在其中对动画做了一些优化,这些优化使得在使用多个属性动画时,更加有效率,节省系统资源。
- AnimatorSet
AnimatorSet——动画集合,用于组合一系列动画,使用AnimatorSet可以实现动画的串行和并行执行。
// 使用AnimatorSet可以实现动画的串行和并行执行
ObjcetAnimator animator1 = ObjectAnimator.ofFloat(mImageView, "rotation", 0f, 360f);
ObjcetAnimator animator2 = ObjectAnimator.ofFloat(mImageView, "traslationX", 0f, 200f);
ObjcetAnimator animator3 = ObjectAnimator.ofFloat(mImageView, "traslationY", 0f, 200f);
// 创建动画集合
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
// set.playTogether(animator1, animator2, animator3); // 并行执行
// set.playSequentially(animator1, animator2, animator3); // 串行执行
set.play(animator2).with(animator2); // 并行执行
set.play(animator1).after(animator2); // 在animator2之后执行,串行
set.start();
属性动画不仅仅可以通过代码实现,也可以通过XML文件来实现,但是实际开发中建议使用代码进行开发。通过代码实现比较简单,并且属性动画的一个属性的起始值很多时候是无法提前确定的。
二、插值器与估值器
定义属性动画的时候,可以通过自定义插值器和估值器实现更多的动画效果。
- 插值器——TimeInterpolator
时间插值器定义了一个时间的映射关系,实现动画在执行时间内的加速、减速等,其作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。
所有插值器都实现了TimeInterpolator接口,自定义插值器是可以通过实现TimeInterpolator接口。
插值器的简单使用代码如下。
AnimatorSet set = new AnimatorSet();
set.playTogether(animator, animator1);
set.setDuration(500);
// 使用减速插值器
set.setInterpolator(new DecelerateInterpolator());
set.start();
系统预置的插值器如下表所示。
插值器 | 描述 |
---|---|
LinearInterpolator | 线性插值 |
AccelerateInterpolator | 加速 |
DecelerateInterpolator | 减速 |
AccelerateDecelerateInterpolator | 先加速,后减速 |
AnticipateInterpolator | 先向后,再向前抛向终点 |
OvershootInterpolator | 向前抛出终点,再回到终点 |
AniticipateOvershootInterpolator | 先向后,再向前抛出终点,再回到终点 |
BounceInterpolator | 结束时反弹 |
CycleInterpolator | 循环播放 |
TimeInterpolator | 用于自定义 |
- 估值器——TypeEvaluator
估值器的作用是根据当前属性改变的百分比来计算改变后的属性值,与属性的起始值、结束值、fraction三个值有关。
默认情况下,默认的Evaluators将对起始值和结束值的间隔,依照时间进行等分,(在不设定Interpolator的情况下)从起始值均匀的增加(减低)到结束值。
例如如下的动画中,默认的Evaluators将依照100ms对间隔1.0f进行等分,因此该动画每1ms增加的透明度为1/100f。
ObjectAnimator.ofFloat(mImageView, "alpha", 0f, 1f)
.setDuration(100)
.start();
系统预置的插值器如下表所示。
估值器(Evaluator) | 描述 |
---|---|
IntEvaluator | 用于评估int型的属性值 |
FloatEvaluator | 用于评估float型的属性值 |
ArgbEvaluator | 用于评估颜色的属性值,采用16进制 |
TypeEvaluator | 用于自定义估值器的接口 |
所有的估值器(Evaluator)都实现了TypeEvaluator接口,通过实现该接口可以自定义估值器,接口如下。其中fraction为比例因子(0.0~1.0),用于计算转化后的值。
public interface TypeEvaluator<T> {
/**
* @param fraction 比例因子
* @param startValue 属性起始值
* @param endValue 属性结束值
* @return 转换后的值
**/
public T evaluate(float fraction, T startValue, T endValue);
}
自定义Evaluator的例子如下:
// 设置起始值,终止值
PointOpsition startPoint = new PointOpsition(0, 0);
PointOpsition endPoint = new PointOpsition(radius, 60 * i + 360);
// 设置估值器,起始值和终止值
ValueAnimator animator = ValueAnimator.ofObject(new ExpandTypeEvaluator(), startPoint, endPoint);
animator.addUpdateListener((valueAnimator) -> {
PointOpsition pointOpsition = (PointOpsition) valueAnimator.getAnimatedValue();
button.setTranslationX(pointOpsition.getX());
button.setTranslationY(pointOpsition.getY());
});
animator.start();
实现了TypeEvaluator接口的估值器:
class ExpandTypeEvaluator implements TypeEvaluator<PointOpsition> {
@Override
public PointOpsition evaluate(float v, PointOpsition pointOpsition, PointOpsition t1) {
float rRaduis = t1.getRadius() * v + (1 - v) * pointOpsition.getRadius();
float rDegree = t1.getDegree() * v + (1 - v) * pointOpsition.getDegree();
return new PointOpsition(rRaduis, rDegree);
}
}
三、属性动画的监听器
属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:AnimatorUpdateListener和AnimatorListener。
AnimatorListener的定义如下:
从该接口定义可以看出,它可以监听动画的开始、结束、取消以及重复播放。同时为了方便开发,系统还提供了AnimatorListenerAdapter,它使得开发者可以选择性的实现上述四个方法。
AnimatorUpdateListener接口的定义如下。该接口会监听整个动画过程,动画由许多帧组成,每播放一帧,onAnimatorUpdate就会被调用一次。
四、对任意属性做动画
属性动画要求动画作用的对象提供该属性的get和set方法。因此对object的属性abc做动画,如果想要让动画生效,要同时满足两个条件:
第一、object必须提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的初始值(如果这条不满足,程序直接Crash)。
第二、object的setAbc属性对属性abc所做的改变必须能够通过某种方法反映出来,比如带来UI的改变(如果这条不满足,动画无效果但不会Crash)。
对于第二种情况,官方文档提供了三种解决思路:
- 给对象加上get和set方法,如果有权限的话。
- 用一个类来包装原对象,间接为其提供get和set方法。
- 采用ValueAnimator,监听动画过程,自定义实现属性的改变。
五、使用动画的注意事项
通过动画可以实现一些比较绚丽的效果,但是在使用View动画、帧动画以及属性动画的过程中需要注意一些事项,如下表所示。
问题 | 注意 |
---|---|
OOM问题 | 该问题出现在帧动画中,当图像数量过多且图片较大时,极易出现OOM |
内存泄漏 | 属性动画中的无限循环动画,在Activity退出时及时停止,否则将导致Activity无法释放而造成内存泄漏。(View动画不存在该问题) |
兼容性问题 | 动画在3.0以下有兼容性问题,在某些特殊情景无法正常工作,需要做好适配 |
View动画问题 | View动画并不是真正改变View的状态,有时候动画完成后无法隐藏View(setVisibility(GONE)无效),这个时候只要调用view.clearAnimation()清除View动画即可解决 |
不要使用px | 尽量使用dp,使用px会导致不同设备上有不同的效果 |
动画元素的交互 | 在Android 3.0以前的系统上,不管是View动画还是属性动画,在View移动后,原位置依旧可以触发单击事件。而3.0以后,属性动画的单击事件触发位置为移动后的位置,View动画仍然在原位置。 |
硬件加速 | 使用动画过程中,建议开启硬件加速,以提高动画流程性 |
参考资料:《Android艺术开发探索》