Android读书笔记(7)—— Android动画深入分析

一、View动画

Android动画分为三种:View动画、帧动画和属性动画。

1、View动画的种类

View动画的作用对象是View,支持四种动画效果:平移、缩放、旋转和透明。上述四种变换效果对应着Animation四个子类: TranslateAnimationScaleAnimationRotateAnimationAlphaAnimation。这四种动画皆可以通过XML定义,也可以通过代码来动态创建。建议采用
xml定义动画,具有更好的可读性。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
//动画插值器,影响动画的播放速度
android:interpolator="@android:anim/accelerate_interpolator"
//表示集合中的动画是否和集合共享一个插值器
android:shareInterpolator="true" 
//表示动画结束以后,View停留在结束动画的位置,如果为false,View会回到动画开始的位置
android:fillAfter="true" >
//透明度动画,对应 AlphaAnimation 类,可以改变 View 的透明度
    <alpha
        android:duration="3000"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
    //旋转动画,对应着 RotateAnimation ,它可以使 View 具有旋转的动画效果
    <rotate
        android:duration="2000"
        android:fromDegrees="0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:startOffset="3000"
        android:toDegrees="180" />
    <!--通过设置第一个alpha动画播放3s后启动rotate动画实现组合动画,如果不设置startOffset则同时播放。
    pivotX:表示旋转时候的相对轴的坐标点,即围绕哪一点进行旋转,默认情况下轴点是 View 中心
    -->
    //平移动画,对应 TranslateAnimation 类,可以使 View 完成垂直或者水平方向的移动效果。
    <translate
        android:fromXDelta="500"
        android:toXDelta="0" />
    //缩放动画,对应 ScaleAnimation 类,可以使 View 具有放大和缩小的动画效果。
    <scale
        android:duration="1000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50"
        android:pivotY="50"
        android:toXScale="2"
        android:toYScale="2" />
    </set>

标签表示动画集合,对应AnimationSet类,可以包含一个或若干个动画,内部还可以嵌套其他动画集合。
android:fillAfter属性表示动画结束以后,View是否停留在结束动画的位置,如果为 false , View 会回到动画开始的位置。这个参数在动画 XML 文件的 </set> 节点中设置或在程序 Java 代码中进行设置:setFillAfter(true)。

定义完View动画的xml后,通过以下代码应用动画:

Aniamation anim = AnimationUtils.loadAnimation(context,R.anim.animation_test);
view.startAnimation(anim);

代码动态创建动画

AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(1500);
view.startAnimation(alphaAnimation);

通过setAnimationListener给View动画添加过程监听

public static interface AnimationListener {
    void onAnimationStart(Animation animation);
    void onAnimationEnd(Animation animation);
    void onAnimationRepeat(Animation animation);
}

2、自定义View动画

除了系统提供的四种动画外,我们可以根据需求自定义动画,自定义一个新的动画只需要继 Animation 这个抽象类,然后重写它的inatializeapplyTransformation这两个方法,在initialize 方法中做一些初始化工作,在applyTransformation方法中进行矩阵变换即可,很多时候采用Camera 来简化矩阵的变换过程。其实自定义动画的主要过程就是矩阵变换的过程,矩阵变换是数学上的概念,需要掌握该方面知识方能轻松实现自定义动画。

3、帧动画

帧动画是顺序播放一组预先定义好的图片,使用简单,但容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。系统提供了另一个类AnimationDrawble来使用帧动画,使用的时候,需要通过XML定义一个AnimationDrawble,如下:

//\res\drawable\frame_animation_list.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">//oneshot 代表着是否只展示一遍
    <item
        android:drawable="@drawable/one"
        android:duration="2000"/>
    <item
        android:drawable="@drawable/two"
        android:duration="2000"/>
    <item
        android:drawable="@drawable/three"
        android:duration="2000"/>
</animation-list>

然后将上述Drawable作为View的背景并播放即可

view.setBackgroundResource(R.drawable.frame_animation_list);
AnimationDrawble drawable = (AnimationDrawble)view.getBackground();
drawable.start();

二、View动画的特殊使用场景

View动画除了可以实现的四种基本的动画效果外,还可以在一些特殊的场景下使用,比如在 ViewGroup 中可以控制子元素的出场效果,在Activity中可以实现不同Activity之间的切换效果。

1、LayoutAnimation

作用于ViewGroup,为ViewGroup指定一个动画,当它的子元素出场时都会具有这种动画效
果,一般用在ListView上。

1.1、一般步骤
  1. 定义一个LayoutAnimation
//res/anim/layout_animation.xml
<?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/zoom_in">
</layoutAnimation>

android:delay
表示子元素开始动画的延时时间,取值为子元素入场动画时间duration的倍数,比如子元素入场动画时间周期为 300ms ,那么 0.5 表示每个子元素都需要延迟 150ms 才能播放入场动画,即第一个子元素延迟 150ms 开始播放入场动画,第二个子元素延迟 300ms 开始播放入场动画,依次类推进行。
android:animationOrder
表示子元素动画的开场顺序,normal(正序)、reverse(倒序)、random(随机)。
android:animation
为子元素指定具体的入场动画

  1. 为 ViewGroup 指定属性
    android:layoutAnimation="@anim/layout_animation"

除了xml中指定LayoutAnimation外,也可以通过LayoutAnimationController来实现

1.2、LayoutAnimationController是实现
//用于控制子 view 动画效果
LayoutAnimationController layoutAnimationController= new LayoutAnimationController(AnimationUtils
.loadAnimation(this,R.anim.zoom_in));
layoutAnimationController.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(layoutAnimationController);
listView.startLayoutAnimation();

2、Activity的切换效果

自定义Activity的切换效果,主要通过overridePendingTransition(int enterAnim, int exitAnim) 方法,enterAnim表示Activity被打开时候所需的动画资源ID,exitAnim是Activity被暂停时所需的动画资源ID。该方法必须要在startActivity(intent)和finish()方法之后调用才会有效。

//启动新的Activity带动画
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
startActivity(intent);
overridePendingTransition(R.anim.zoom_in,R.anim.zoom_out);
//退出Activity本身带动画
@Override
public void finish() {
    super.finish();
    overridePendingTransition(R.anim.zoom_in,R.anim.zoom_out);
}

Fragment也可以添加切换动画,通过 FragmentTransation 中的setCustomAnimations() 方法来实现切换动画,这个动画需要的是 View 动画,不能使用属性动画,因为属性动画也是 API11 才引入的,不兼容。

三、属性动画

属性动画是 API 11 引入的新特性,属性动画可以对任何对象做动画,甚至还可以没有对象。可以在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。与View动画相比,属性动画几乎无所不能,只要对象有这个属性,它都能实现动画效果。API11以下可以通过nineoldandroids库来兼容以前版本。

1、使用属性动画

属性动画有ValueAnimatorObjectAnimatorAnimatorSet等概念。其中ObjectAnimator继承自ValueAnimator,AnimatorSet是动画集合。

改变一个对象 TranslationY 属性,让其沿着 Y 轴平移一段距离

private void translateViewByObjectAnimator(View targetView){
//TranslationY 目标 View 要改变的属性
//ivShow.getHeight() 要移动的距离
    ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(targetView,"TranslationY",ivShow.getHeight());
    objectAnimator.start();
}

改变一个对象的背景色属性,3秒内从0xFFFF8080到0xFF8080FF渐变,无限循环且有反转效果

 private void changeViewBackGroundColor(View targetView){
     ValueAnimator valueAnimator=ObjectAnimator.ofInt(targetView,"backgroundColor", Color.RED,Color.BLUE);
     valueAnimator.setDuration(3000);
     //设置估值器,该处插入颜色估值器
     valueAnimator.setEvaluator(new ArgbEvaluator());
     //无限循环
     valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
     //反转模式
     valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
     valueAnimator.start();
 }

动画集合,5 秒内对 View 旋转、平移、缩放和透明度进行了改变

 private void startAnimationSet(View targetView){
     AnimatorSet animatorSet=new AnimatorSet();
     animatorSet.playTogether(ObjectAnimator.ofFloat(targetView,"rotationX",0,360),
              //旋转
             ObjectAnimator.ofFloat(targetView,"rotationY",0,360),
             ObjectAnimator.ofFloat(targetView,"rotation",0,-90),
              //平移
             ObjectAnimator.ofFloat(targetView,"translationX",0,90),
             ObjectAnimator.ofFloat(targetView,"translationY",0,90),
              //缩放
             ObjectAnimator.ofFloat(targetView,"scaleX",1,1.5f),
             ObjectAnimator.ofFloat(targetView,"scaleY",1,1.5f),
              //透明度
             ObjectAnimator.ofFloat(targetView,"alpha",1,0.25f,1));
             animatorSet.setDuration(3000).start();
 }

也可以通过在xml中定义在 res/animator/ 目录下。具体如下:

 \res\animator\value_animator.xml
 <?xml version="1.0" encoding="utf-8"?><!--set 标签对应着 AnimatorSet-->
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="together">
     <!--对应着 ObjectAnimator-->
     <objectAnimator
         android:propertyName="x"
         android:repeatCount="infinite"
         android:repeatMode="reverse"
         android:startOffset="10"
         android:valueTo="300"
         android:valueType="floatType" />
     <!--其中propertyName 属性设置为translationX ,valueType 设置为floatType 可以正常启动
        如果 valueType 设置为 intType 将报错,即属性类型必须为 floatType 类型,并且android:propertyName="translationX" 表示移动 300 ,而  android:propertyName="x"表示移动到300 ,是两个不同属性-->
     <!--startOffset 指定延迟多久开始播放动画-->
     <!--valueType 表示指定的 android:propertyName 所指定属性的类型,intType 表示指定属性是整型的,
      如果指定属性为颜色,那么不需要指定 valueType 属性,系统会自动处理
      repeatCount 表示动画循环次数,默认值为0,-1 表示无限循环-->

     <!--对应着 ValueAnimator-->
    <animator
     android:duration="300"
     android:valueFrom="0"
     android:valueTo="360"
     android:startOffset="10"
     android:repeatCount="infinite"
     android:repeatMode="reverse"
     android:valueType="intType"/>
 </set>

使用动画

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context , R.animator.anim);
set.setTarget(view);
set.start();

实际开发中建议使用代码实现属性动画。很多时候一个属性的起始值是无法提前确定的。

2、理解插值器和估值器

时间插值器( TimeInterpolator)
根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator( 线性插值器:匀速动画) ,AccelerateDecelerateInterpolator( 加速减速插值器:动画两头慢中间快) ,DecelerateInterpolator(减速插值器:动画越来越慢)。

估值器(类型估值算法, TypeEvaluator)
根据当前属性改变的百分比来计算改变后的属性值。系统预置有IntEvaluator(针对整型属性) 、FloatEvaluator(浮点型属性) 、ArgbEvaluator(针对 Color 属性)。

如图所示,表示一个匀速动画,采用了线性插值器和整型估值算法,在 40ms 内,View 的 X 属性实现了从 0 到 40 的变化。

属性动画要求对象的该属性有 set 和 get(可选) 方法,插值器和估值算法除了系统提供的外,我们还可以自己定义,插值器或者估值算法都是一个接口,且内部只有一个方法,我们只需要派生一个类实现该接口即可,然后就可以做出千变万化的动画效果了。具体而言是:自定义插值器需要实现 Interpolator 或者 TimeInterpolator ,自定义估值算法需要实现 TypeEvaluator 。如果要对其他类型(非int,float,color)做动画,必须要自定义类型估值算法。

3、属性动画的监听器

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

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

为了方便开发,系统提供了AnimatorListenerAdapter类,它是AnimatorListener的适配器类,可以有选择的实现以上4个方法。

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

AnimatorUpdateListener会监听整个动画的过程,动画由许多帧组成的,每播放一帧,onAnimationUpdate就会调用一次。利用这个特性,我们可以做一些特殊的事情。

4、对任意属性做动画

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

  • object 必须要提供 setAbc() 方法,如果动画的时候没有传递初始值,那么还要提供 getAbc() 方法,因为系统要去取 abc 属性的初始值(如果这条不满足,程序直接crash)。
  • object 的 setAbc() 对属性 abc 所做的改变必须能够通过某种方法反应出来(即最终体现了 UI 的变化),比如会带来 UI 的改变之类(如果这条不满足,动画无效果,但是程序不会crash)。

我们给 Button 的 width 属性做动画无效果但是没有crash的原因就是 Button 内部提供了 setWidth 和 getWidth 方法,但是这个 setWidth 方法并不是改变 UI 大小的,而是用来设置最大宽度和最小宽度的。对于上面属性动画的两个条件来说,这个例子只满足了条件 1 而未满足条件 2。

针对上面问题,官方文档给出了 3 种解决方法:

  • 请给你的对象加上get和set方法,如果你有权限的话
    对于SDK或者其他第三方类库的类无法加上的
  • 用一个类来包装原始对象,间接为其提供get和set方法
 /**
  * 将 Button 沿着 X 轴方向放大
  * @param button
  */
private void performAnimationByWrapper(View button){
     ViewWrapper viewWrapper=new ViewWrapper(button);
     ObjectAnimator.ofInt(viewWrapper,"width",800)
        .setDuration(5000)
        .start();
}
 
private class ViewWrapper {
    private View targetView;
    public ViewWrapper(View targetView) {
        this.targetView = targetView;
    }
    public int getWidth() {
        //注意调用此函数能得到 View 的宽度的前提是, View 的宽度是精准测量模式,即不可以是 wrap_content
        //否则得不到正确的测量值
        return targetView.getLayoutParams().width;
    }
    public void setWidth(int width) {
        //重写设置目标 view 的布局参数,使其改变大小
        targetView.getLayoutParams().width = width;
        //view 大小改变需要调用重新布局
        targetView.requestLayout();
    }
}
  • 采用ValueAnimator,监听动画过程,自己实现属性的改变
    ValueAnimator 本身不作用于任何对象,也就是说直接使用它没有任何动画效果(所以系统提供了它的子类 ObjectAnimator 供我们直接使用,作用于对象直接执行动画效果,而 ValueAnimator 只是提供改变一个值的过程,并能监听到整个值的改变过程,我们基于这个过程可以自己去实现动画效果,在这个过程中做想要达到的效果,自己去实现)。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们对象的属性,这样我们自己就实现了对对象做了动画。
 //new 一个整型估值器,用于下面比例值计算使用(可以自己去计算,这里直接使用系统的)
 private IntEvaluator intEvaluator = new IntEvaluator();
 private void performAnimatorByValue(final View targetView, final int start, final int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //获取当前动画进度值
            int currentValue = (int) animation.getAnimatedValue();
            //获取当前进度占整个动画比例
            int fraction = (int) animation.getAnimatedFraction();
            //直接通过估值器根据当前比例计算当前 View 的宽度,然后设置给 View
            targetView.getLayoutParams().width = intEvaluator.evaluate(fraction, start, end);
            targetView.requestLayout();
        }
    });
    valueAnimator.setDuration(5000)
        .start();
 }

5、属性动画的工作原理

属性动画需要运行在有Looper的线程中,系统通过反射调用被作用对象get/set方法。

四、使用动画的注意事项

  • OOM问题
    使用帧动画时,当图片数量较多且图片分辨率较大的时候容易出现OOM,需注意,尽量避免使用帧动画。
  • 内存泄漏
    使用无限循环的属性动画时,在Activity退出时即使停止,否则将导致Activity无法释放从而造成内存泄露。
  • 兼容性问题
    动画在3.0以下的系统存在兼容性问题,特殊场景可能无法正常工作,需做好适配工作。
  • View动画的问题
    View动画是对View的影像做动画,并不是真正的改变了View的状态,因此有时候会出现动画完成后View无法隐藏( setVisibility(View.GONE) 失效) ,这时候调用 view.clearAnimation() 清理View动画即可解决。
  • 不要使用px
    使用px会导致不同设备上有不同的效果。
  • 动画元素的交互
    View动画是对View的影像做动画,View的真实位置没有变动,动画完成后的新位置是无法触发点击事件的。属性动画是真实改变了View的属性,所以动画完成后的位置可以接受触摸事件。
  • 硬件加速
    使用动画的过程中,使用硬件加速可以提高动画的流畅度。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容