7.3.4 对任意属性做动画
这里先提出一个问题:给Button
加一个动画,让这个Button
的宽度从当前宽度增加到500px
。也许你会说,这很简单,用View
动画就可以搞定。很快你就会恍然大悟,原来View
动画根本不支持对宽度进行动画。没错,View
动画只支持四种类型:平移,旋转,缩放,不透明度。当然用X
方向缩放(scaleX)
可以让Button
在X
方向放大,看起来好像是宽度增加了,实际上不是,只是Button
被放大了而已,而且由于只X
方向被放大,这个时候Button
的背景以及上面的文本都被拉伸了,甚至有可能Button
会超出屏幕。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
ObjectAnimator.ofInt(button,"width",500).setDuration(2000).start();
break;
}
}
上述代码运行后发现没效果,其实没效果是对的,如果随便传递一个属性过去,轻则没动画效果,重则程序直接Crash
。
属性动画的原理:属性动画要求动画作用的对象提供该属性的get
和set
方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set
方法,每次传递给set
方法的值都不一样,确切来说,随着时间的推移,所传递的值越来越接近最终值。总结一下,我们对object
的属性abc
做动画,如果想让动画生效,要同时满足两个条件。
object
必须要提供setAbc
方法,如果动画的时候没有传递初始值,那么还要提供getAbc
方法,因为系统要去取abc
属性的初始值(如果这条不满足,程序直接Crash
)。object
的setAbc
对属性abc
所做的改变必须能够通过某种方法反应出来,比如会带来UI
的改变之类的(如果这条不满足,动画无效果但不会Crash
)。
以上条件缺一不可,Button
内部虽然提供了getWidth
和setWidth
方法,但是这个setWidth
方法并不是改变视图的大小,它是TextView
新添加的方法,View
是没有这个setWidth
方法的,由于Button
继承了TextView
,所以Button
也就有了setWidth
方法。
打开TextView
的源码,可以发现getWidth
的确是获取View
的宽度的,但是setWidth
是设置TextView
的最大宽度和最小宽度的,这个和TextView
的宽度不是一个东西。具体来说,TextView
的宽度对应XML
中的android:layout_width
属性,而TextView
还有一个属性android:width
,这个android:width
属性就对应了TextView
的setWidth
方法。总之,TextView
和Button
的setWidth
,getWidth
干的不是同一件事情,通过setWidth
无法改变控件的宽度,所以对width
做属性动画没有效果。对应于属性动画的两个条件来说,本例中动画不生效的原因是只满足了条件1
而未满足条件2
。
针对上述问题,官方文档上告诉我们有3种解决方法:
- 给你的对象加上
get
和set
方法,如果你有权限的话。 - 用一个类来包装原始对象,间接为其提供
get
和set
方法。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
ViewWrapper wrapper = new ViewWrapper(button);
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(2000).start();
break;
}
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View mTarget) {
this.mTarget = mTarget;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
提供了ViewWrapper
类专门用于包装View
,然后我们对ViewWrapper
的width
属性做动画,并且在setWidth
方法中修改其内部的target
的宽度,而target
实际上就是我们包装的Button
。
- 采用
ValueAnimator
,监听动画过程,自己实现属性的改变。
ValueAnimator
本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程。在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
performAnimate(button,button.getWidth(),500);
break;
}
}
private void performAnimate(final View target,final int start,final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// 持有一个IntEvaluator对象,方便下面估值的时候使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获得当前动画的进度值,整型,1~100之间
int currentValue = (int) animation.getAnimatedValue();
// 获得当前进度占整个动画过程的比例,浮点型 0~1之间
float fraction = animation.getAnimatedFraction();
// 直接调用整型估值器,通过比例计算出宽度,然后在设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
它会在5000ms
内将一个数从1
变到100
,然后动画的每一帧会回调onAnimationUpdate
方法。在这个方法里,我们可以获取当前的值(1~100)
和当前值所占的比例,我们可以计算出Button
现在的宽度应该是多少。比如时间过了一半,当前值是50
,比例为0.5
,假设Button
的起始宽度是100px
,最终宽度是500px
,那么Button
应该增加的宽度是400 * 0.5 = 200
,那么当前Button
的宽度应该为初始宽度 + 增加宽度(100 + 200 = 300)
。
7.3.5 属性动画的工作原理
属性动画要求动画作用的对象提供该属性的set
方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set
方法。每次传递给set
方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get
方法,因为系统要去获取属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多。
7.4 使用动画的注意事项
通过动画可以实现一些比较绚丽的效果,但是在使用过程中,也需要注意一些事情,主要分为下面几类:
1. OOM问题
这个问题主要出现在帧动画中,当图片数量较多且图片较大时就极易出现OOM,这个在实际的开发中要尤其注意,尽量避免使用帧动画。
2. 内存泄漏
在属性动画中有一类无限循环的动画,这类动画需要在Activity
退出时及时停止,否则将导致Activity
无法释放从而造成内存泄漏,通过验证后发现View
动画并不存在此问题。
3. 兼容性问题
动画在3.0
以下的系统上有兼容性问题,在某些特殊场景可能无法正常工作,因此要做好适配工作。
4. View动画的问题
View
动画是对View
的影像做动画,并不是真正地改变View
的状态,因此有时候会出现动画完成后View
无法隐藏的现象,即setVisibility(View.GONE)
失效了,这个时候只要调用view.clearAnimation()
清除View
动画即可解决此问题。
5. 不要使用px
在进行动画的过程中,要尽量使用dp
,使用px
会导致在不同的设备上有不同的效果。
6. 动画元素的交互
将View
移动(平移)后,在Android3.0
以前的系统上,不管是View
动画还是属性动画,新位置均无法触发单击事件,同时,老位置仍然可以触发单击事件。尽管View
已经在视觉上不存在了,将View
移回原位置以后,原位置的单击事件继续生效。从3.0
开始,属性动画的单击事件触发位置为移动后的位置,但是View
动画仍然在原位置。
7. 硬件加速
使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。