安卓的动画可以分为三种:View动画,帧动画和属性动画。
帧动画是View动画的一种

view动画 对场景里的对象进行图片变换(平移,缩放,旋转,透明度)从而产生动画效果,是一种渐进式动画,并且支持自定义。

帧动画 通过顺序播放一系列图像从而产生效果,简单理解为图片切换动画。

属性动画 动态的改变对象的属性从而到达动画效果

View动画

View动画的种类

View动画的四种变换效果对应着Animation的四个子类:TranslateAnimation(平移动画)、ScaleAnimation(缩放动画)、RotateAnimation(旋转动画)和AlphaAnimation(透明度动画),他们即可以用代码来动态创建也可以用XML来定义,推荐使用可读性更好的XML来定义。

要使用View动画,首先要创建动画的XML文件,这个文件的路径为:res/anim/filename.xml。View动画的描述文件是由固定的语法的,如下:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator=""
    android:shareInterpolator="false">
    
    <alpha
        android:fromAlpha=""
        android:toAlpha=""/>
    <scale
        android:fromXScale=""
        android:fromYScale=""
        android:pivotX=""
        android:pivotY=""
        android:toXScale=""
        android:toYScale=""/>
    <translate
        android:fromXDelta=""
        android:fromYDelta=""
        android:toXDelta=""
        android:toYDelta=""/>
    <rotate
        android:fromDegrees=""
        android:toDegrees=""
        android:pivotY=""
        android:pivotX=""/>
</set>

从上面的语法看,View动画既可以是单个动画,也可以由一系列动画组成。

标签

标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且他的内部也可以嵌套其他动画集合。他包含两个属性

android:interpolator
表示动画集合所采用的插值器,插值器影响动画的速度。

android:shareInterpolator
表示集合中的动画是否和集合共享同一个插值器。

标签

表示平移动画,对应translateAnimation类

android:fromXDelta:表示x的起始值,比如0;
android:toXDelta:表示x的结束值,比如100;
android:fromYDelta:表示y的起始值;
android:toYDelta:表示y的结束值。

标签

缩放动画,对应ScaleAnimation
android:fromXScale:水平方向缩放的起始值,如0.5;
android:toXScale:水平方向缩放的结束值,比如1.2;
android:fromYScale:竖直方向缩放的起始值;
android:toYScale:竖直方向缩放的结束值;
android:pivotX:缩放的轴点的x坐标,它会影响缩放的效果;
android:pivotY:缩放的轴点的y坐标,它会影响缩放的效果。

标签

旋转动画,对应RotateAnimation
android:fromDegress:旋转开始的角度,比如0;
android:toDegress:旋转结束的角度,比如180;
android:pivotX:旋转的轴点的x坐标;
android:pivotY:旋转的轴点的y坐标。

标签

透明度动画,对应AlphaAnimation
android:fromAlpha:表示透明度的起始值,比如0.1;
android:toAlpha:表示透明度的结束值,比如1;

View动画还有一些常用的属性:

android:duration:动画的持续时间
android:fillAfter:动画结束以后View是否停留在结束位置,true表示View停留在结束位置,false则不停留

帧动画

帧动画是顺序播放一组预先定义好的图片,类似于电影播放。不同于View动画,系统提供了另外一个类AnimationDrawable来使用帧动画。帧动画的使用比较简单,首先需要通过XML定义一个AnimationDrawable,如下:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
	android:oneshot="false">
    <item android:drawable="@drawable/image1" android:duration="500"/>
    <item android:drawable="@drawable/image2" android:duration="500"/>
    <item android:drawable="@drawable/image3" android:duration="500"/>
</animation-list>

然后将上述的Drawable作为View的背景并通过Drawable来播放动画即可:

Button button = findViewById(R.id.button);
button.setBackgroundResource(R.drawable.frame_animation);
AnimationDrawable drawable = (AnimationDrawable)button.getBackground();
drawable.start();

帧动画的使用比较简单,但是比较容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。

自定义View动画

除了系统提供的四种View动画,还可以自定义动画View动画。

自定义View动画只需要继承Animation抽象类,然后重写它的initialize和applyTransformation方法,在initialize方法中做初始化处理,在applyTransformation中进行相应矩阵变换即可。

View动画的特殊使用场景

对于View动画除了上述四种形式,还可以在一些特殊场景下使用,比如控制ViewGroup子元素的出场效果,Acitivity与Activity之间切换效果。

LayoutAnimation

LayoutAniamtion作用于ViewGroup,为ViewGroup指定一个动画,这样当他的子元素出场的时候都具有这种效果。这种效果常用于ListView上,这样它的每个子项布局item都以一定动画的形式出现。
1.首先定义一个LayoutAnimation,文件路径res/anim/anim_layout.xml:

//res/anim/anim_layout.xml
<layoutAnimation xmln:android="http://schemas.android.com/apk/res/android"
	android:delay="0.5"
	android:animationOrder="normal"
	android:animation="@anim/anim_item"/>

各个属性含义如下:
android:delay:表示子元素开始动画的时间延迟,是百分比,比如子元素入场动画时间周期300ms,那么0.5表示每个子元素延迟150ms(50%)才能播放入场动画。
android:animationOrder:表示子元素动画的顺序,有三种:normal、reverse和random,其中normal表示顺序显示,即排在前面的先播放;reverse表示逆向显示;random则是随机播放动画。
android:animation:为子元素指定具体的入场动画

2.为子元素指定具体的入场动画

//res/anim/anim_item
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
	android:duration="300">

	<alpha
		android:fromAlpha="0.0"
		android:toAlpha="1.0"/>

	<translate
		android:fromXDelta="500"
		android:toXDelta="0"/>

</set>

3.为 ViewGroup指定android:layoutAnimation属性:android:layoutAnimation=“@anim/anim_layout”。同样对于ListView来说,所有的子项布局item出场时都有出场动画了,如下:

<ListView
	android:id="@+id/list"
	android:layout_height="match_parent"
	android:layout_width="match_parent"
	android:layoutAnimation="@anim/anim_layout"/>

除了在通过XML文件定义及指定LayoutAnimation,还可以通过LayoutAnimationController来实现:

ListView listView = findViewById(R.id.list);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);

Activity的切换效果

Activity有默认的切换效果,这个效果我们可以自定义,通过**overridePendingTransition(int enterAnim, int exitAnim)**这个方法,这个方法必须在startActivity之后或finish之前调用才能生效,参数含义如下:

enterAnim:Activity被打开时,所需动画资源id
exitAnim:Activity被暂停时,所需动画资源id

当启动一个Activity时,如下添加切换效果:

Intent intent = new Intent(this,TestActivity.class);
stratActivity(intent);
overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);

对于Fragment也可以添加切换动画,通过FragmentTransaction的setCustomAnimation()方法添加切换动画,为View动画。

属性动画

使用属性动画

属性动画通过动态地改变对象的属性达到动画效果。属性动画可以对任意对象的属性进行动画不仅是View,动画默认时间间隔300ms,默认帧率10ms/帧。属性动画可以通过代码实现,也可以通过XML文件实现,但建议使用代码,因为简单。常用的几个动画类是:ValueAnimator(直接使用没有动画效果)、ObjectAnimator和AnimatorSet,其中ObjectAnimator继承自ValueAnimator,AnimatorSet是动画集合,可以定义一组动画。下面是一些例子:

方式一:通过代码定义属性动画

1.改变一个对象的translationY属性,让其沿着Y轴向上平移一段距离,如果没指定起始位置会通过get方法获取起始位置,通过set方法设置属性。简单使用如下

ObjectAnimator.ofFloat(myObject, "translationY", myObject.getHeight()).start();

2.改变一个对象的背景色属性,如改变View的背景色,下面定义一个动画使背景色在3s内实现从0xFFFF8080到0XFF8080FF的渐变,动画会无限循环且会有反转效果(都可以设置,后面介绍

ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", 0xFFFF8080, 0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimation.REVERSE);
colorAnim.start();

3.动画集合AnimatorSet,5s内实现对View的旋转、平移、缩放和透明度进行改变

AnimatorSet set = new AnimatorSet();
set.playTogether(
	ObectAnimator.ofFloat(myView, "rotationX", 0, 360),
	ObectAnimator.ofFloat(myView, "rotationY", 0, 180),
	ObectAnimator.ofFloat(myView, "rotation", 0, -90),
	ObectAnimator.ofFloat(myView, "translationX", 0, 90),
	ObectAnimator.ofFloat(myView, "translationY", 0, 90),
	ObectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
	ObectAnimator.ofFloat(myView, "scaleY", 1, 1.5f),
	ObectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5*1000).start();

方式二:通过XML文件定义使用属性动画
文件路径res/animator/目录下,语法如下:

//res/animator/property_animator.xml
<set android:ordering=["together" | sequentially"]>

	<objectAnimator
		android:propertyName="string"
		android:duration="int“
		android:valueFrom=”float | int | color"
		android:valueTo="float | int | color"
		android:startOffset="int"
		android:repeatCount="int"
		android:repeatMode="["restart" | "reverse"]
		android:valueType=["intType" | floatType]/>
		
	<animator
		android:duration="int"
		android:valueFrom=”float | int | color"
		android:valueTo="float | int | color"
		android:startOffset="int"
		android:repeatCount="int"
		android:repeatMode="["restart" | "reverse"]
		android:valueType=["intType" | floatType]/>

	<set>
		...
	</set>

</set>

在XML文件中定义属性动画,标签对应AnimatorSet类,标签对应ValueAnimator类,标签对应ObjectAnimator类。标签的android:ordering属性有两个可选值:“together"和"sequentially”,“together"表示动画集合中子动画同时播放,“sequentially"表示动画集合中子动画按前后顺序依次播放,默认值是"together”。
对于标签,下面看看各个标签的属性:
android:propertyName:表示属性动画的作用对象的属性的名称(就是改变的属性);
android:duration:表示动画的时长;
android:valueFrom:表示属性的起始值;
android:valueTo:表示属性的结束值;
android:startOffset:表示动画的延迟时间,动画开始后,需要延迟多少毫秒才会播放此动画;
android:repeatCount:表示动画的重复次数,默认值为0,其中-1表示无限循环;
android:repeateMode:表示动画的重复模式,有两个选项"restart"和"reverse”,分别表示连续重复播放和逆向播放。连续重复播放就是每次重新开始播放,逆向播放则是指播放一次后,下一次倒着播放,所以第一次从头开始,第二次倒着,第三次倒回来了,第四次又是倒着如此重复;
android:valueType:表示android:propertyName中指定的属性的类型,有"intType"和“floatType"两种,分别代表整型和浮点型。如果属性是color则不需要只当valueType,系统会自动处理。

对于一个动画来说,有两个属性需要特殊说明一下,一个是android:repeatCount,它表示动画循环的次数,默认值是0,其中-1表示无限循环;另一个是android:repeatMode,它表示动画的循环模式,有两个选项:“restart”和“reverse”,分别表示连续重复和逆向重复。连续重复比较好理解,就是动画每次都从头开始播放,而逆向重复是指第一次播放完以后,第二次会倒着播放动画,第三次再从头开始播放动画,第四次再倒着播放,如此反复。

例子:

//res/animtor/property_animator.xml
<set android:ordering="together">
	<objectAnimator
		android:propertyName="x"
		android:duration="300"
		android:valueTo="200"
		android:valueType="intType"/>
	<objectAnimator
		android:propertyName="y"
		android:duration="300"
		android:valueTo="300"
		android:valueType="intType"/>
</set>

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,R.anim.property_animator);
set.setTarget(mButton);
set.start();

实际开发中建议使用第一种也就是通过代码来定义使用属性动画,因为通过代码更简单。其次很多时候我们是不知道一个属性的起始值的,例如移动一个Button从屏幕左边至右边,由于不知道屏幕宽度,无法在XML中定义属性动画,这种情况就必须使用代码实现。

理解插值器和估值器

TimeInterpolator中文翻译为时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画),AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等。TypeEvaluator的中文翻译为类型估值算法,也叫估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有IntEvaluator(针对整型属性),FloatEvaluator(针对浮点型属性)和ArgbEvaluator(针对Color属性)。属性动画中的插值器和估值器很重要,它们是实现非匀速动画的重要手段。

如图,他表示的是一个匀速动画,采用了线性插值器和整形估值算法,在40ms内,x属性的值从0-40的变换:
在这里插入图片描述
由于动画的默认刷新率为10ms/帧,所有该动画将分5帧进行,我们来考虑一下第三帧,当时间为20ms的时候,百分比为0.5,意味着时间过去了一半,那x改变了多少?其实x也是0.5,为什么因为他使用了线性的插值器也就是匀速动画。

@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

很显然,线性插值器的返回值和输入值是一样的,因为都是0.5,这意味着x的改变值是0.5,这个时候插值器的工作就完成了。具体x变成了什么值是需要估值算法来确定的,查看整型估值算法的源码:

public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

上述的算法很简单,evaluate的三个参数分别表示估算的小数,开始值和结束值,对于我们的例子而言分别是0.5 0 40 ,根据上述算法,整型估算器返回给我们的结果是20,这就是(x= 20 t = 20ms)的由来。

属性动画的监听器

属性动画监听器用于监听动画的播放过程,主要有两个接口:AnimatorUpdateListener和AnimatorListener 。

AnimatorListener

public static interface AnimatorListener {
    void onAnimationStart(Animator animation); //动画开始
    void onAnimationEnd(Animator animation); //动画结束
    void onAnimationCancel(Animator animation); //动画取消
    void onAnimationRepeat(Animator animation); //动画重复播放
}

系统还提供了AnimatorListenerAdapter这个类,他是AnimatorListener的适配器,这样我们就可以有选择的实现上面的4个方法。

AnimatorUpdateListener:

public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animator);
}

AnimatorUpdateListener比较特殊,他会监听整个动画过程,动画是由许多帧组成的,每播放一帧,onAnimationUpdate就会被调用一次。

对任意属性做动画

这里先提出一个问题:给Button加一个动画,让这个Button的宽度从当前宽度增加到500px。但是View动画根本不支持对宽度进行动画。View动画只支持四种类型:平移(Translate),旋转(Rotate),缩放(Scale),透明度(Alpha)。当然用x方向上的缩放可以让Button在x方向放大,看起来好像是宽度增加了,实际上不是,只是Button被放大了而已,而且由于只x方向上被放大,这个时候Button的背景以及上面的文本都被拉伸了,甚至有可能Button会超出屏幕。
再来试试属性动画,如下

ObjectAnimator.onInt(mButton,"width",500).setDuration(5000).start();

上述代码运行后发现没有效果。

下面分析属性动画的原理:属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,我们对object的属性abc做动画,如果想要动画生效,要同时满足两个条件:

object必须提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的初始值(如果这条不满足,程序直接crash)。

object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类的(如果这条不满足,动画无效果但不会crash)。

以上条件缺一不可。那么我们对Button的width属性做动画为什么会没有效果?这是因为Button内部虽然提供了getWidth和setWidth方法,但是这个setWidth方法并不是改变视图的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,由于Button继承了TextView,所以Button也就有了setWidth方法。下面是setWidth和getWidth方法的源码:

public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();
        invalidate();
}
public final int getWidth() {
        return mRight - mLeft;
}

从上述源码可以看出,getWidth的确是获取View的宽度的,而setWidth是TextView及其子类的专属方法,它的作用不是设置View的宽度,而是设置TextView的最大宽度和最小宽度的,这个和TextView的宽度不是一个东西。具体来说,TextView的宽度对应XML中的android:layout_width属性,而TextView还有一个属性android:width,这个属性就对应了TextView的setWidth方法。总之,TextView和Button的setWidth和getWidth针对的不是同一属性,通过setWidth无法改变控件的宽度,所以对width做属性动画没有效果。

针对上述问题,官方文档告诉我们有3种解决方法:

1.给你的对象加上get和set方法,如果你有权限的话;

2.用一个类来包装原对象,间接为其提供get和set方法;

3.采用ValueAnimator,监听动画过程,自己实现属性的改变。

给你的对象加上get和set方法,如果你有权限的话

这个意思很好理解,如果你有权限的话,加上get和set就搞定了。但是很多时候我们没有权限这么做。比如上面对于width的问题,你无法给Button加上一个合乎要求的setWidth方法,因为这是Android SDK内部实现的。这个方法最简单,但是往往是不可行的。

方法二:用一个类来包装原对象,间接为其提供get和set方法

这是一个很有用的解决方法,因为用起来方便,也很好理解,如下:

private void performAnimate(){
    ViewWrapper wrapper = new ViewWrapper(mButton);
    ObjectAnimator.ofInt(wrapper,"width",wrapper.getWidth(),500).setDuration(5000).start();
}

public void onClick(View v){
    if(v == mButton){
        performAnimate();
    }
}

private static class ViewWrapper{
    private View mTarget;
    
    public ViewWrapper(View target){
        mTarget = target;
    }
    
    public int getWidth(){
        return mTarget.getLayoutParams().width;
    }
    
    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

上述代码在5s内让Button的宽度增加到了500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体本例是包装Button。然后我们对ViewWrapper的width属性做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button。

方法三:采用ValueAnimator,监听动画过程,自己实现属性的改变

首先说说什么是ValueAnimator,ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。如下:

private void performAnimate(final View target,final int start,final int end){
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
    valueAnimator.addUpdateListener(new AnimatorUpdateListener(){
        private IntEvaluator mEvaluator = new IntEvaluator();
       
        public void onAnimationUpdate(ValueAnimator animator){
            //获得当前动画的进度值,整型,1-100
            int currentValue = (Integer)animator.getAnimatedValue();
            //获得当前进度占整个动画的过程的比例,浮点型,0-1
            float fraction = animator.getAnimatedFraction();
            //直接调用整型估值器,通过比例计算出宽度,然后再设给Button
            target.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
            target.requestLayout();
        }
    });
    
    valueAnimator.setDuration(5000).start();
}

上述代码的效果图和采用ViewWrapper是一样的。

属性动画的工作原理

属性动画要求动画作用的对象提供该属性的get/set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画效果多次去调用set方法,每次传递的set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值,总结一下,我们对object的属性abc做动画,如果想让动画生效,要同时满足两个条件:
1.object必须要提供set方法,如果动画的时候没有传递初始值,那么我们还要提供get方法,因为系统要去取abc的属性(如果这条不满足,程序直接Crash)

2.object的set方法对abc所做的改变必须通过某种方法反应,比如带来UI的改变(如果这条不满足,动画无效果但是不会Crash)

使用动画的注意事项

使用动画的注意事项
1.OOM问题:当我们使用帧动画时,当图片数量较多且图片较大时就极易出现OOM。
2.内存泄漏:属性动画中有一类无限循环的动画(setRepeatCount(-1)),当Activity退出时要及时停止该类动画,否则将导致Activity无法释放从而造成内存泄漏,View动画不存在此问题。
3.兼容性问题:属性动画是3.0以后才出现的,要注意。
4.View动画的问题:View动画实际是对View的影像做动画,并不是真正的改变View的状态。这就会引发之前在滑动时讲过的问题,通过View动画平移View后,假如View有点击事件,平移后位置是不能触发而原位置可以触发点击事件的,就是这个原因。
5.不要使用px:在进行动画过程中要尽量使用dp,使用px会导致在不同的设备上有不同的效果。
6.硬件加速:使用动画过程中,建议开启硬件加速,可以提高动画流畅性。(4.0默认开启了硬件加速)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐