第七章 Android动画深入分析
Android的动画可以分成三种:view动画,帧动画,属性动画。view动画是通过对场景的对象不断做图像交换(平移、缩放、旋转、透明度)而产生的动画效果,渐进式可自定义。帧动画就是播放一系列图片产生动画效果(过多会导致OOM)。属性动画通过动态的改变对象的属性达到动画效果(低版本可通过兼容库使用属性动画)。本章内容:View动画以及自定义View动画的方式,View动画的一些特殊的使用场景,属性动画以及使用动画的注意事项。
(一)View动画
View动画的作用对象是view,它支持四种动画:平移,缩放,旋转和透明度。
1.1.View动画的种类
view动画的变换效果对应着Animation的四个子类,分别是TranslateAnimation,ScaleAnimation,RotateAnimation,AlphaAnimation,这四种动画可以通过xml来定义,也可以代码来实现。建议用XML自定义动画:
1.1.1.使用XML进行View动画
如果要使用View动画,首先要创建动画的XML文件,View的动画既可以是单一的动画,也可以组合在一起而set标签就是组合动画,对应着AnimationSet。在res/anim目录下。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:shareInterpolator="true">
<!--set标签标示动画集合,对应AnimationSet类,可包含若干个动画,内部也可以嵌套其他动画。-->
<!--android:interpolator:表示动画集合所使用的插值器,插值器影响动画的速度,比如非匀速动画就需要插值器来制作动画的过程,这个属性可以不指定,默认为加速减速插值器-->
<!--android:shareInterpolator:表示集合中的动画是否和集合共享同一个插值器,如果集合不指定插值器,那么子动画就需要单独的去指定所需要的插值器了-->
<!--透明度动画,改变View的透明度,对应的是AlphaAnimation,fromAlpha表示透明度的起始值,比如0.1;toAlpha表示透明度结束值。-->
<alpha
android:duration="400"
android:fromAlpha="0.1"
android:toAlpha="1" />
<!--缩放动画,对应的ScaleAnimation,view具有放大,缩小的动画效果-->
<!--fromXScale、toXScale 水平方向缩放的起始值和结束值;pivotX、pivotY是缩放轴点(轴点位置适当)的x、y坐标,它会影响缩放的效果。-->
<scale
android:duration="300"
android:fromXScale="0.5"
android:fromYScale="0.5"
android:pivotX="10"
android:pivotY="10"
android:toXScale="1.2"
android:toYScale="1.2" />
<!--平移动画,水平和竖直方向完成平移;对应TranslateAnimation类,-->
<!--fromXDelta、fromYDelta表示x、y的起始值;android:toXDelta、android:toYDelta标示x、y的结束值。-->
<translate
android:duration="200"
android:fromXDelta="10"
android:fromYDelta="10"
android:toXDelta="20"
android:toYDelta="20" />
<!--旋转动画,对应RotateAnimation,它可以让view旋转。-->
<!--fromDegrees、toDegrees 旋转开始、结束的角度;pivotX、pivotY旋转轴点的x、y-->
<rotate
android:duration="2000"
android:fromDegrees="0"
android:pivotX="10"
android:pivotY="10"
android:toDegrees="180" />
<!--重点:android:duration表明动画持续的时间;android:fillAfter动画结束之后是否停留在结束的位置-->
</set>
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_click = (Button) findViewById(R.id.btn_click);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.testset);
btn_click.startAnimation(animation);
}
1.1.2.使用代码进行View动画
将一个Button的透明度在10s内由0变为1。
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(10000);
btn_click.setAnimation(alphaAnimation);
1.2.自定义View动画
除了系统提供的四种动画以外,我们还可以自定义View动画,派生出来新动画只继承animation这个抽象类,重写它的initialize和applyTransformation方法。initialize完成初始化工作;applyTransformation进行相应的矩阵变化。很多时候需要采用Camera来简化矩阵变换过程。较少使用。示例:围绕y轴旋转并沿着z轴平移实现一种类似于3D的效果。
代码示例:
public class Rotate3dAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
public Rotate3dAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse) {
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
1.3.帧动画
帧动画就是顺序的播放一组图片,系统提供了一个AnimationDrawable来实现帧动画,帧动画的使用比较简单。一直颜色在变化。帧动画使用简单,但容易引起OOM,避免过多尺寸较大图片。
XML定义:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@color/colorAccent"
android:duration="1000" />
<item
android:drawable="@color/colorPrimary"
android:duration="1000" />
<item
android:drawable="@color/what"
android:duration="1000" />
</animation-list>
代码中定义:
btn_click.setBackgroundResource(R.drawable.animationdrawable01);
AnimationDrawable drawable = (AnimationDrawable) btn_click.getBackground();
drawable.start();
(二)View动画的特殊使用场景
除了四种View方式外,View动画可以在一些特殊场景下使用:ViewGroup控制子元素的出场效果,activity的切换动画。
2.1.LayoutAnimation
LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样他的子元素出场的时候就会具有这种动画了,这种效果常常在listview上,我们时常会看到一种特殊的listview,他的每一个item都有一个动画,使用LayoutAnimation实现。LayoutAnimation是View动画
2.1.1.为ViewGroup的子元素加上出场效果
步骤一:定义LayoutAnimation;
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="normal"
android:animation="@anim/testlayoutanimation">
<!--android:delay 子元素开始动画的延迟,假设子元素入场动画的周期为300ms,那么0.5表示每一个子元素都需要延迟150ms才能播放入场动画-->
<!--android:animationOrder 表示子元素动画的顺序,有三种模式,normal,random,reverse,分别表示顺序执行、表示随机、倒序播放。-->
<!--android:animation 为子元素指定具体入场动画-->
</layoutAnimation>
步骤二:为子元素指定的入场动画;
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100"
android:toYDelta="100" />
<rotate
android:duration="400"
android:fromDegrees="0"
android:toDegrees="90" />
</set>
步骤三:为ViewGroup指定layoutanimation属性,对于listview来说,这样item就具有出场动画了。
ListView listView = (ListView) layout.findViewById(R.id.list);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);
2.1.2.Activity的切换效果
利用overridePendingTransition(int enterAnim, int exitAnim)这个方法,这个方法必须在startactivity或者finish之后调用才是有效的,里面的两个参数也很简单,是进出的动画。有点秀。
btn_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,Main2Activity.class);
startActivity(intent);
overridePendingTransition(R.anim.testset01,R.anim.testset01);
}
});
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.testset01,R.anim.testset01);
}
(三)属性动画
属性动画是API11加入的特性,它对作用对象进行了扩展,可对任何对象甚至没有对象做动画。属性动画中有ValueAnimator、ObjectAnimator、AnimatorSet等。
3.1使用属性动画
属性动画可对任意对象的属性进行动画而不仅仅是view,动画默认的时间间隔是300ms,默认帧率是10ms/帧。能够在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。API11之前没有属性动画,可使用兼容库譬如nineoldandroids使用属性动画。
比较常用的属性动画类是:ValueAnimator、ObjectAnimator(继承自ValueAnimator)、AnimatorSet(动画集合)等。
3.1.1.使用代码完成属性动画
场景一:改变一个对象的translationY属性,让其沿着Y轴向上平移一个时间,默认时间完成。
代码示例一:
ObjectAnimator.ofFloat(imageView,"translationY",-imageView.getHeight()).start();
场景二:改变一个对象的背景颜色值,譬如改变view的背景,下面的动画是让view的背景从0xffff8080到0xff8080ff,动画会无限循环、反转。
代码示例二:
ValueAnimator colorAnim = ObjectAnimator.ofInt(imageView,"backgroundColor",0xFFF8080,
0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatCount(ValueAnimator.REVERSE);
colorAnim.start();
场景三:动画集合,5s内对View的旋转,平移,缩放和透明度进行了改变。代码示例三:
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(imageView, "rotationX",0,360),
ObjectAnimator.ofFloat(imageView, "rotationY",0,180),
ObjectAnimator.ofFloat(imageView, "rotation",0,-90),
ObjectAnimator.ofFloat(imageView, "translationX",0,90),
ObjectAnimator.ofFloat(imageView, "translationX",0,90),
ObjectAnimator.ofFloat(imageView, "scaleX",1,1.5f),
ObjectAnimator.ofFloat(imageView, "scaleY",1,0.5f),
ObjectAnimator.ofFloat(imageView, "alpha",1,0.25f,1)
);
set.setDuration(5*1000).start();
3.1.2.使用XML完成属性动画
属性动画除了代码实现外,还可以通过XML实现。属性动画需要定义在res/animator目录下。在XML中可以定义ValueAnimator、ObjectAnimator和AnimatorSet,分别与之对应的标签是<animator>、<objectAnimator>、<set>。<set>的android:ordering属性有两个可选值:”together”和”sequentially”。前者表示动画集合的子动画同时播放;后者表示动画集合按照前后顺序依次播放。默认值为together。
代码示例:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:duration="300"
android:propertyName="x"
android:valueTo="200"
android:valueType="intType" />
<!--objectAnimator标签中的各属性:-->
<!--android:propertyName:表示属性动画作用对象的属性的名称-->
<!--android:duration:表示动画的时长-->
<!--android:valueFrom:表示属性的起始值-->
<!--android:valueTo:表示属性的结束值-->
<!--android:startOffset:表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正的播放-->
<!--android:repeatCount:表示动画的重复次数,默认为零,-1表示无限循环-->
<!--android:repeatMode:表示动画的重复模式,有一个restart(连续播放)和reverse(逆向播放)模式-->
<!--android:valueType:表示propertyName有两个属性有int和float两个可选项,分别表示属性的类型,和浮点型,-->
<!--另外,如果所制定的是颜色类型,那么就不需要指定propertyName,系统会自动对颜色类型进行处理-->
<objectAnimator
android:duration="300"
android:propertyName="y"
android:valueTo="300"
android:valueType="intType" />
<!--<animator-->
<!--android:duration="1000"-->
<!--android:repeatCount="infinite"-->
<!--android:repeatMode="restart"-->
<!--android:startOffset="15"-->
<!--android:valueFrom="0.5dp"-->
<!--android:valueTo="1.0dp"-->
<!--android:valueType="colorType" />-->
</set>
如何调用?
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(getApplicationContext(), R.anim.animatorxm);
set.setTarget(imageView);
set.start();
3.1.3.如何选取属性动画的方式?
建议用代码实现,一方面代码会比较简单,另一方面属性的起始值有时无法提前确定,eg:一个view需要从左边移动到右边,但是如果是用XML实现,无法提前知道屏幕的宽度,也无法将属性动画定义在XML中。
3.2.理解插值器和估值器
TimeInterpolator(时间插值器)的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。系统预设的是LinearInterpolator(线性插值器,匀速动画),AccelerateDecelerateInterpolator(加速减速插值器:两头慢中间快)和DecelerateInterpolator(减速插值器)。
TypeEvaluator(类型估值算法),也叫估值器,它的作用是根据当前属性变化的百分比来计算变化后的属性值。系统预设了针对整型属性IntEvaluator,浮点型FloatEvaluator,和color颜色值ArgbEvaluator。两者结合能实现非匀速动画。
动画的默认刷新率为10ms/帧,所有该动画将分5帧进行,我们来考虑一下第三帧(x=20,t=20ms),当时间为20ms的时候,时间流逝百分比为20/40 = 0.5,那x改变了多少?插值器返回0.5,估值算法会根据0.5 0 40返回结果20。当然也可以自定义估值算法和插值器。
3.3.属性动画的监听器
属性动画提供了监听器用于监听动画的播放过程,主要有两个接口AnimationUpdateListener和AnimationListener。
3.3.1.AnimationListener
AnimatorListener 监听了动画开始,结束,取消和重复,为了方便开发,系统提供了AnimatorListenerAdapter这个类,他是适配器,这样我们就可以选择实现下面的方法。
public static interface AnimatorListener {
//监听了动画开始
void onAnimationStart(Animator animation);
//监听了动画结束
void onAnimationEnd(Animator animation);
//监听了动画取消
void onAnimationCancel(Animator animation);
//监听了动画重复
void onAnimationRepeat(Animator animation);
}
3.3.2.AnimationUpdateListener
会监听整个动画过程,每播放一帧,就会被调用一次下面的方法,可以利用此特性做事情。
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
3.4.对任意属性做动画
场景:给Buttion设置一个动画,让它的宽度从当前的变成500px,可以用view动画来搞定,但存在拉伸后超出屏幕的风险,利用属性动画试试。
解决方法:
Click事件:ObjectAnimator.ofInt(btn_click02,"width",500).setDuration(500).start();
如果想要一个动画生效,必须同时满足两个条件:(1)object必须要提供set方法,如果动画的时候没有传递初始值,那么我们还要提供get方法,因为系统要去取abc的属性(如果这条不满意,程序直接Crash);(2)object的set方法对该属性所做的改变必须通过某种方法反映,比如带来UI的改变。(如果这条不满足,动画无效果但是不会Crash)。
如果动画无效,那么应该:(1)给你的对象增加set/get方法,前提是你有权限的话(往往不可行);(2)用一个类来包装原始对象,间接提供get/set方法(使用ViewWrapper专门包装View,对View属性的width属性做动画,并修改内部宽度);(3)采用ValueAnimator,监听动画过程,自己去实现属性变化(基本上是整形估值器IntEvaluator的内部实现)。
第二种方法的代码实现:
private void performAnimate_01() {
ViewWrapper wrapper = new ViewWrapper(btn_click02);
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(500).start();
}
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();
}
}
第三种方法的代码实现:
performAnimator(btn_click02,btn_click02.getWidth(),500);
private void performAnimator(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();
//直接使用整形估值器,通过比例计算宽度,然后再设置给按钮
target.getLayoutParams().width = mEvaluator.eval(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
3.5.属性动画的工作原理
以上面代码ObjectAnimator.ofInt(btn_click02,"width",500).setDuration(500).start();为例。
步骤一:先看ObjectAnimator的start方法。判断是否有相同的动画,是的话去掉,接下来调用了父类ValueAmiator的start方法;
步骤二:父类ValueAnimator的start方法:属性动画运行在Lopper线程中,最终会调用AnimationHandler的start方法,它是一个runnable对象。然后调用ValueAnimator的doAnimationFrame方法。
步骤三:ValueAnimator的doAnimationFrame方法:调用animationFrame方法,而animationFrame内部调用了animateValue。calculateValue方法计算每一帧动画所对应的属性值。属性值的get和set方法是通过反射调用完成的。
(四)使用动画的注意事项
可能存在的问题:
1.OOM问题(内存耗尽)
帧动画中图片过多过大的时候容易OOM了,实际开发尽量避免使用帧动画;
2.内存泄漏
在属性动画中有一类无限循环的动画,在activity退出后不停止的话,可能就会导致Activity无法释放而导致的内存泄漏。
3.兼容性问题
属性动画在3.0以下的系统上有缺陷,最好做好适配工作。
4.View动画的问题
view动画对view的影像做动画,并不是真正的改变view的状态,eg:有时候会动画完成后view无法隐藏的现象,setVisibility(View.GONE)失效,记得调用clearAnimation清除动画。
5.不要使用px
在进行动画的过程,要尽量使用dp,使用px会导致适配问题。注:px(像素,1px代表屏幕上一个物理的像素点):不被建议使用,因为同样100px的图片,在不同手机上显示的实际大小可能不同,而dp是像素密度。系数*dp长度获得像素数,适配性较好。(文字的尺寸一律用sp单位,非文字的尺寸一律使用dp单位,px顶多用于画一条切割线)
6.动画元素的交互
将view移动后,3.0以前的系统,不管是view动画还是属性动画,新位置都无法调用单机事件,同时老位置却可以,从3.0之后,属性动画单击事件发生于移动后的位置,但是view动画的点击事件仍然在原位置。