Android 视图动画(View Animation) 使用详解

谨以文章记录学习历程,如有错误还请指明。

动画综述

Google大大对动画的总述如下:

Animations can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.

没错,放上原文我只是装个逼,~
简单来说,动画就两个作用:

  • 添加可视提示,通知我们这个APP中正在发生的事情。比如用户界面发生变化时,有新的内容加载或某些操作变为可用。
  • 提供高逼格的外观(装逼利器

动画的分类如下:

动画分类

视图动画(View Animation)

补间动画(Tween Animation)

概述

  • 作用于视图对象View,如TextView,不可作用于属性,如点击响应位置等
  • 通过确定开始的视图样式 & 结束的视图样式、中间动画变化过程由系统补全来确定一个动画
  • 动画可以在视图对象的内容上执行一系列简单的转换(位置、大小、旋转和透明度)。如果你有一个TextView对象,你可以移动,旋转,缩放文本。如果它有一个背景图像,背景图像将随着文本一起被转换。
  • XMLJAVA代码定义。建议使用XML文件,因为它比更可读、可重用和可切换

分类

根据动画效果,补间动画分为以下4类

  • 透明度动画(alpha)
  • 缩放动画(scale)
  • 平移动画(Translate)
  • 旋转动画(rotate)

不同动画与Java类、xml文件关键字对应关系如下:

名称 Java子类 xml关键字 说明
透明度动画 AlphaAnimation <alpha> 放置在res/anim/目录下 透明度渐变
旋转动画 RotateAnimation <rotate> 放置在res/anim/目录下 视图旋转
缩放动画 ScaleAnimation <scale> 放置在res/anim/目录下 放大/缩小 视图尺寸大小
平移动画 TranslateAnimation <translate> 放置在res/anim/目录下 视图位置移动
复合动画 AnimationSet <set> 放置在res/anim/目录下 一个持有其它动画元素alpha、scale、translate、rotate或者其它set元素的容器

详细说明

补间动画的继承关系:

继承关系

由于Animation是抽象基类,其提供了一些通用的动画属性方法,如下所示

xml属性 Java方法 说明
android:detachWallpaper setDetachWallpaper(boolean) 是否在壁纸上运行
android:duration setDuration(long) 动画的运行时间(以毫秒为单位);必须设置
android:fillAfter setFillAfter(boolean) 动画结束时是否保持动画最后的状态;默认为false,优先于fillBefore
android:fillBefore setFillBefore(boolean) 动画结束时是否还原到开始动画前的状态;默认为true
android:fillEnabled setFillEnabled(boolean) 是否应用fillBefore的值,对fillAfter无影响;默认为true
android:interpolator setInterpolator(Interpolator) 设定插值器(指定的动画效果,譬如回弹等)
android:repeatCount setRepeatCount(int) 重复次数
android:repeatMode setRepeatMode(int) 重复类型有两个值,reverse表示倒序回放,restart表示从头播放
android:startOffset setStartOffset(long) 调用start函数之后等待开始运行的时间,单位为毫秒
android:zAdjustment setZAdjustment(int) 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal


透明度动画(Alpha)-- 属性
xml属性 Java方法 说明
android:fromAlpha AlphaAnimation(float fromAlpha, …) 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明)
android:toAlpha AlphaAnimation(…, float toAlpha) 动画结束的透明度,同上


旋转动画(Rotate)-- 属性
xml属性 Java方法 说明
android:fromDegrees RotateAnimation(float fromDegrees, …) 旋转开始角度,正代表顺时针度数,负代表逆时针度数
android:toDegrees RotateAnimation(…, float toDegrees, …) 旋转结束角度,正代表顺时针度数,负代表逆时针度数
android:pivotX RotateAnimation(…, float pivotX, …) 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY RotateAnimation(…, float pivotY) 缩放起点Y坐标,同上规律


缩放动画(Scale)-- 属性
xml属性 Java方法 说明
android:fromXScale ScaleAnimation(float fromX, …) 初始X轴缩放比例,1.0表示无变化
android:toXScale ScaleAnimation(…, float toX, …) 结束X轴缩放比例
android:fromYScale ScaleAnimation(…, float fromY, …) 初始Y轴缩放比例
android:toYScale ScaleAnimation(…, float toY, …) 结束Y轴缩放比例
android:pivotX ScaleAnimation(…, float pivotX, …) 缩放起点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY ScaleAnimation(…, float pivotY) 缩放起点Y轴坐标,同上规律


平移动画(Translate)-- 属性
xml属性 Java方法 说明
android:fromXDelta TranslateAnimation(float fromXDelta, …) 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:fromYDelta TranslateAnimation(…, float fromYDelta, …) 起始点Y轴从标,同上规律
android:toXDelta TranslateAnimation(…, float toXDelta, …) 结束点X轴坐标,同上规律
android:toYDelta TranslateAnimation(…, float toYDelta) 结束点Y轴坐标,同上规律


具体使用

下面我们直接以复合动画为例,演示如何通过XML以及JAVA两种方式使用动画

XML方式

  • 文件位置:
    res/anim/filename.xml
  • 资源引用
    Java: R.anim.filename
    XML: @[package:]anim/filename
  • 语法:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>
  • 创建动画
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation myAnimation= AnimationUtils.loadAnimation(this, R.anim.filename);
spaceshipImage.startAnimation(myAnimation);

以上就是一个标准的XML方式使用自定义的补间动画的模板

JAVA方式

示例如下:

ImageView imageView = findViewById(R.id.image_view);
        // 创建 需要设置动画的 视图View

        // 组合动画设置
        AnimationSet setAnimation = new AnimationSet(true);
        // 创建组合动画对象(设置为true)

        // 设置组合动画的属性
        setAnimation.setRepeatMode(Animation.RESTART);
        ...

        // 逐个创建子动画,不作过多描述

        // 子动画1:透明度动画
        Animation alpha = new AlphaAnimation(1,0);
        alpha.setDuration(3000);
        ...

        // 子动画2:缩放动画
        Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        scale1.setDuration(1000);
        ...
        
        // 子动画3:平移动画
        Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
                TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
                TranslateAnimation.RELATIVE_TO_SELF,0
                ,TranslateAnimation.RELATIVE_TO_SELF,0);
        translate.setDuration(10000);
        ...
        
        // 子动画4:旋转动画
        Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        rotate.setDuration(1000);
        rotate.setRepeatMode(Animation.RESTART);
        rotate.setRepeatCount(Animation.INFINITE);
        ...
        

        // 将创建的子动画添加到组合动画里
        setAnimation.addAnimation(alpha);
        setAnimation.addAnimation(rotate);
        setAnimation.addAnimation(translate);
        setAnimation.addAnimation(scale1);

        imageView.startAnimation(setAnimation);
        // 播放动画

首选XML方式使用动画(不意味着XML方式全面优于JAVA方式)

  • XML方式:可读性好
  • JAVA方式:可动态创建动画效果

至于补间动画的使用,Animation还有如下一些比较实用的方法介绍:

Animation类的方法 解释
reset() 重置Animation的初始化
cancel() 取消Animation动画
start() 开始Animation动画
hasStarted() 判断当前Animation是否开始
hasEnded() 判断当前Animation是否结束

既然补间动画只能给View使用,那就来看看View中和动画相关的几个常用方法吧,如下:

View类的常用动画操作方法 解释
startAnimation(Animation animation) 对当前View开始设置的Animation动画
clearAnimation() 取消当View在执行的Animation动画

特别特别注意:补间动画执行之后并未改变View的真实布局属性值。切记这一点,譬如我们在Activity中有一个Button在屏幕上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的Button是没有任何反应的,而点击原来屏幕上方没有Button的地方却响应的是点击Button的事件。

动画监听

  • 动画监听器接收来自动画的通知。通知表示与动画相关的事件,例如动画的结束或重复
  • 使用方法:
Animation.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //动画开始时执行
          }
      
           @Override
          public void onAnimationRepeat(Animation animation) {
              //动画重复时执行
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //动画取消时执行
          }
    
          @Override
          public void onAnimationEnd(Animation animation) {
              //动画结束时执行
          }
      });
  • 补间动画的监听只能通过此方式,且必须要复写全部4个方法。
  • 属性动画时,可以采用Animator.addListener(new AnimatorListenerAdapter(){ //复写指定方法 })------动画适配器AnimatorListenerAdapter中已经实现好每个接口

插值器Interpolator

简介

插值器是在XML中定义的一个动画修改器,它影响动画的变化率。这允许现有动画附加加速、减速、重复、反弹等效果。

继承关系

系统为我们提供了上图中的各种插值器,其都是实现了Interpolator接口的实现类,具体说明如下:

java类 XML 资源ID 说明
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速再减速
AccelerateInterpolator @android:anim/accelerate_interpolator 持续加速
AnticipateInterpolator @android:anim/anticipate_interpolator 先退后再加速前进
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 先退后再加速前进,超出终点后再回终点
BounceInterpolator @android:anim/bounce_interpolator 结束时弹球效果
CycleInterpolator @android:anim/cycle_interpolator 周期运动
DecelerateInterpolator @android:anim/decelerate_interpolator 减速
LinearInterpolator @android:anim/linear_interpolator 匀速
OvershootInterpolator @android:anim/overshoot_interpolator 向前弹出一定值之后回到原来位置(快速完成动画,超出再回到结束样式)

使用方法

  • XML方式
<set android:interpolator="@android:anim/accelerate_interpolator">
    ...
</set>
  • JAVA方式
Animation alphaAnimation = new AlphaAnimation(1,0);
                alphaAnimation.setDuration(3000);
                //创建插值器对象
                Interpolator interpolator = new OvershootInterpolator();
                //为动画添加插值器
                alphaAnimation.setInterpolator(interpolator);
                imageView.startAnimation(alphaAnimation);

自定义插值器

某些情景可能你会发现系统提供的插值器不能满足需求,此时我们需要自定义插值器。有两种实现方式:XML自定义实现JAVA代码实现方式

XML实现
  1. res/anim/下创建filename.xml
  2. 修改插值器属性(如下不作任何修改,则与系统预设插值器功能相同)
<?xml version="1.0" encoding="utf-8"?>
<InterpolatorName xmlns:android="http://schemas.android.com/apk/res/android"
    android:attribute_name="value"
    />
  1. 补间动画中引用该文件(资源引用为@[package:]anim/filename

XML实现方式本质就是修改系统提供的插值器的某些属性,具体可修改属性如下:

插值器 可修改属性 属性说明
<accelerateDecelerateInterpolator> 无属性
<accelerateInterpolator> android:factor Float,加速速率(默认值为1)
<anticipateInterpolator> android:tension Float. 起始点后拉的张力数(默认值为2)
<anticipateOvershootInterpolator> android:tension
android:extraTension
Float. 起始点后拉的张力数(默认值为2)
Float. 拉力的倍数(默认值为1.5)
<bounceInterpolator> 无属性
<cycleInterpolator> android:cycles Integer. 循环次数(默认为1)
<decelerateInterpolator> android:factor Float. 减速的速率(默认为1)
<linearInterpolator> 无属性
<overshootInterpolator> android:tension Float. 超出终点后的张力(默认为2)
JAVA方式

在此只讨论原理,不设计具体逻辑(奈何我是个数学渣,公式真的是一头雾水= =)

在前面继承关系的图中,我们可以看出所有插值器继承自BaseInterpolator,其又实现了Interpolator接口,而Interpolator接口继承自TimeInterpolator接口,TimeInterpolator接口中的唯一抽象方法为getInterpolation(float input),这个方法是由系统调用的,其中的参数input代表动画的时间,在0和1之间,也就是开始和结束之间。

我们可以看一下系统提供的插值器,如AccelerateDecelerateInterpolator,其源码极其极其简单,如下:

@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {
    public AccelerateDecelerateInterpolator() {
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

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

可以看到,核心方法就是前面提到的getInterpolation(),其效果就是通过这一数学公式得来的,好久不看数学,已经快看不懂这个公式结果是什么了,汗颜= =

public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

由此我们总结以下JAVA方式自定义插值器:

  • 根据需求实现TimeInterpolator接口
  • 实现抽象方法getInterpolation(),在该方法中处理逻辑
  • 为动画设置该 自定义插值器

帧动画(Frame Animation)

概述

  • 作用于视图对象View,如TextView,不可作用于属性,如点击响应位置等
  • 将动画拆分为帧的形式,且每一帧都是一张图片,本质就是按序播放一组预先定义好的图片
  • 两种实现方式:XML & JAVA,依旧推荐XML方式(你问我为什么还是推荐XML?连Google API中都只介绍XML方式,还不能说明问题么)

继承关系


我们很清楚的看到,其本质是Drawable,,因此帧动画的XML定义文件放在res/drawable/目录下

使用说明

XML方式

  1. res/drawable/下创建filename.xml文件
  2. 设置图片资源,示例如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource_name"
        android:duration="integer" />
    //如下
    <item android:drawable="@drawable/wheel0" android:duration="50" />
    <item android:drawable="@drawable/wheel1" android:duration="50" />
    <item android:drawable="@drawable/wheel2" android:duration="50" />
    <item android:drawable="@drawable/wheel3" android:duration="50" />
    <item android:drawable="@drawable/wheel4" android:duration="50" />
    <item android:drawable="@drawable/wheel5" android:duration="50" />
</animation-list>
  1. 启动动画
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);

rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();
参数说明

<animation-list>:必须是根节点,包含一个或者多个<item>元素,包含属性如下:

  • android:oneshottrue代表只执行一次,false循环执行。
  • <item> 类似一帧的动画资源。

<item>animation-list的子项,包含属性如下:

  • android:drawable 一帧的Drawable资源。
  • android:duration 一帧显示多长时间。

JAVA方式

<-- 直接从drawable文件夹获取动画资源(图片) -->
        animationDrawable = new AnimationDrawable();
        for (int i = 0; i <= 25; i++) {
            int id = getResources().getIdentifier("a" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 100);
        }

        <-- 开始动画 -->
        btn_startFrame.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animationDrawable.setOneShot(true);
                iv.setImageDrawable(animationDrawable);
                // 获取资源对象
                animationDrawable.stop();
                 // 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
                animationDrawable.start();
                // 启动动画
               
            }
        });

         <-- 停止动画 -->
        btn_stopFrame.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animationDrawable.setOneShot(true);
                iv.setImageDrawable(animationDrawable);
                animationDrawable.stop();
            }
        });

特别注意,AnimationDrawablestart()方法不能在ActivityonCreate()方法中调运,因为AnimationDrawable还未完全附着到window上,所以最好的调运时机是onWindowFocusChanged()方法中。

相关方法说明

XML属性和Java方法的对应关系如下:

XML属性 Java方法 说明
android:drawable addFrame(Drawable frame, int duration)第一个参数 帧的Drawable的引用
android:duration addFrame(Drawable frame, int duration)第二个参数 显示此帧的时间(以毫秒为单位)
android:oneshot setOneShot(boolean oneShot) 如果true,动画将只运行一次,然后停止。
android:visible setVisible(boolean, boolean). 初始可见状态;默认值为false。

一些常用的方法如下:

方法 说明
start() 从第一帧开始动画
stop() 停止动画,显示当前帧
getFrame() 取得某帧的Drawable
isRunning() 动画是否正在运行

特殊说明

  • 优点:使用简单、方便
  • 缺点尽量避免使用尺寸较大的图片,否则会引起OOM

总结


欢迎关注whd_Alive的简书

  • 不定期分享Android开发相关的技术干货,期待与你的交流,共勉。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,577评论 0 9
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 5,951评论 1 38
  • 转载一篇高质量博文,原地址请戳这里转载下来方便今后查看。1 背景不能只分析源码呀,分析的同时也要整理归纳基础知识,...
    Elder阅读 1,898评论 0 24
  • 一: 传统 View 动画(Tween/Frame) 1.1 Tween 动画 主要有 4 中:缩放、平移、渐变、...
    dfg_fly阅读 661评论 1 2
  • 今天下午,干老师给我们上了一节手机课,我们现是下软件,现场下的哦,然后我们全都进了群,干老师的指令如下: ...
    逍遥_0455阅读 361评论 0 0