Android动画实现详解

pic.jpg

源码传送门

前言

我们都知道,漂亮的用户界面是衡量一款应用"好坏"很重要的依据,因为人都是视觉动物,就好比说花容月貌总有男人为之倾倒,英俊潇洒总能博得芳心。这是一个不容置疑的事实,那么我们的应用也是如此,一个漂亮的用户交互界面能提升用户对应用的好感,提升用户体验。而动画是提升用户体验的一个重要因素,好的动画交互让人用着更舒心,那么今天的这篇文章就是介绍Android中动画实现,让我们的应用动起来。

Android动画分类

在Android中我们一般将动画分为两类,一类是View Animation(视图动画),一类是Property Animation,当然也有说分为三种,Frame Animation,Tween Animation,和Property Animation,由于前两种一般都是作用于整个View的,所以就统称为View Animation。在谷歌官方文档中也是按两种分类来讲解的。

在Android 5.0开始增加了Material Design ,Material Design 中实现了一些动画为用户提供操作反馈并在用户与您的应用进行互动时提供视觉连续性。 它将为按钮与操作行为转换提供一些默认动画,我们可以定制触摸反馈,使用揭露效果,定制操作行为转换,指定定制转换,使用转换启动一个操作行为,以共享元素启动一个操作行为等等。

Frame Animation

由于Frame Animation,Tween Animation的实现还是有区别的,暂且就将这两种方式实现分开介绍,Frame Animation其实就是将一系列的图片一张一张的展示出来,这个和我们看电影是类似的,由于人眼有一个可接收的暂留时间,这也就是为什么人在线看视频会感觉卡顿。当然我们实现Frame Animation就是这个依据,当播放相同的图片张数用时越短也就越流畅,自然人就会感觉是一个动画。我们常见的Gif其实也是帧动画,如果你用PhotoShop打开Gif动画,会发现里面是有很多图层的也就是很多帧。

对于Frame动画有两种实现方式,第一种方式就是在drawable文件夹下创建一个xml文件。它的语法很简单,如下

<?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" />
</animation-list>

看了上面你会发现实现Frame动画很简单,属性很少,animation-list 是动画的根元素,在根元素中的oneshot属性表示动画执行次数,如果设置为true表示只播放一次,如果false则表示会一直循环执行。在根元素下有item元素,该元素就是我们要添加的图片,每一个item表示一帧,item下的drawable就是我们的图片资源,duration就是该帧动画执行的时间。例如

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@mipmap/run1"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run2"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run3"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run4"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run5"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run6"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run7"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run8"
        android:duration="200" />
</animation-list>

使用方法如下

//可以在xml中设置background   
     imageView.setBackgroundResource(R.drawable.frame_run_animation);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();
frame.gif

运行效果图如上,在上面我们没有添加oneshot属性,则该属性默认false,也就是说该动画会一直循环执行,当我们设置true后则播放到最后一帧时动画停止,当我们想停止时可以使用AnimationDrawable 的stop方法,它还提供了isRunning()方法判断是否已经在执行动画。另外我们还需要注意的是小心OOM。

当然用代码实现也很简单,如下

    private void addFrame() {
        AnimationDrawable animationDrawable = new AnimationDrawable();
        int[] mipmaps = new int[]{R.mipmap.run1, R.mipmap.run2, R.mipmap.run3,
                R.mipmap.run4, R.mipmap.run5, R.mipmap.run6, R.mipmap.run7, R.mipmap.run8,};
        for (int i = 1; i <= 8; i++) {
            int id=mipmaps[i-1];
            //或者使用下面方式,注意如果图片资源放在drawable下时将mipmap修改下
            //int id = getResources().getIdentifier("run" + i, "mipmap", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 200);
        }
        animationDrawable.setOneShot(false);
        imageView.setBackgroundDrawable(animationDrawable);
        animationDrawable.start();
    }

Tween Animation

Tween Animation即补间动画,主要分为四种,分别是平移、缩放、旋转、透明度,直接上语法

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    //插值器
    android:interpolator="@[package:]anim/interpolator_resource"
    //动画结束后View是否停留在结束的位置
    android:fillAfter=["true" | "false"] 
    //重复的模式,默认为restart,即重头开始重新运行,reverse即从结束开始向前重新运行
   android:repeatMode="restart/reverse"
    //子元素是否共用此插值器
    android:shareInterpolator=["true" | "false"] >
    <alpha
        //开始透明度0.0(全透明)到1.0(完全不透明)
        android:fromAlpha="float"
        android:toAlpha="float"
        //动画执行时间
        android:duration="integer" />
    <scale 
        //其实X缩放大于0,1的时候表示不缩放,小于1缩小,大于1放大
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
       //缩放中心,也可以穿fraction值
        android:pivotX="float"
        android:pivotY="float"
        android:duration="integer" />
    <translate
        //表示 x 的起始值
        android:fromXDelta="float/fraction"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" 
        android:duration="integer" />
    <rotate
        //起始的旋转角度
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float"
        android:duration="integer"  />
    <set>
        ...
    </set>
</set>

这是官方给的语法,set 是一个动画集合,内部可以是多个动画的组合,也可以嵌套set,这里包含了动画实现的所有属性。在上面的语法中我们需要注意的是平移的时候其实位置接受百分比数值:从-100到100的值,以“%”结尾,表示百分比相对于自身;从-100到100的值,以“%p”结尾,表示百分比相对于父容器。例如平移开始位置在自身中间则是50%,如平时开始位置在父容器的则是50%p.

例如有些人给我们的Activity会加一些从左边进右边出的动画,那么当我们打开Activity时将Activity布局的fromXDelta值-100%p并将toXDelta为0%p,那么我们看到的效果就是从左边进入了。

插值器

在动画插值器起的作用主要是改变动画的执行速率,一般情况我们不需要自己实现插值器,因为在Android中已经给我们提供了9种插值器,应该够我们使用了,我们使用插值器后会让动画执行的效果更酷炫,当然想自定义插值器也不难,可以查看已经实现插值器源码做参考。

  • accelerate_decelerate_interpolator:先加速后减速
  • accelerate_interpolator:一直加速
  • anticipate_interpolator: 开始的时候先向后甩一点然后向前,就好比人扔东西会先向后甩一下,这样才能抛的远
  • anticipate_overshoot_interpolator:和上一种插值器相似,先向后抛然后向前,到达终点后会有回弹一下效果,好比我们将球抛到墙上,然后反弹回来
  • bounce_interpolator:动画结束的时候弹起,类似皮球落地,会弹几下才停止
  • cycle_interpolator:动画循环播放特定的次数回到原点,速率改变沿着正弦曲线
  • decelerate_interpolator:减速的插值器,刚开始速度快,然后越来越慢直到停止
  • linear_interpolator:线性的插值器。从开始到结束匀速运动
  • overshoot_interpolator:向前超过设定值一点然后返回

下面简单实现一个动画,动画效果如下面截图,是一个透明度,平移,缩放的动画同时执行的动画。

frame1.gif
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/bounce_interpolator"
    android:shareInterpolator="true">
    <translate
        android:duration="400"
        android:fromXDelta="50%p"
        android:fromYDelta="-50%p"
        android:toXDelta="0%"
        android:toYDelta="0%" />
    <scale
        android:duration="400"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:toXScale="1.0"
        android:toYScale="1.0" />
    <alpha
        android:duration="400"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
</set>

然后使用下面代码给ImageView加入动画。

 Animation animation= AnimationUtils.loadAnimation(this,R.anim.anim_in_left_top);
  imageView.startAnimation(animation);

当然我们也可给动画加上监听。如

animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                    }
                    @Override
                    public void onAnimationEnd(Animation animation) {
                    }
                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }
                });

上面的监听分别是动画开始结束和更新时候的回调。我们在回调中做一些额外的操作。在代码中实现动画就不在细说,主要对应AnimationSetTranslateAnimationScaleAnimationAlphaAnimationRotateAnimation

Propterty Animation

属性动画是3.0之后引入的,在View动画中虽然我们看到了我们的控件位置发生发生变化,比如Button虽然位置变化了,但是点击响应区域还在原来的位置。而属性动画就可以解决这种问题。它可以作用于View的属性。
语法

<set
//执行的顺序together同时执行,sequentially连续执行
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
      //属性动画名字,例如alpha" 或者"backgroundColor"等
        android:propertyName="string"
        android:duration="int"
        //属性的开始值
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        //该动画开始的延迟时间
        android:startOffset="int"
        //动画重复次数,-1表示一直循环,1表示循环一次也就是播放两次,默认0,播放一次
        android:repeatCount="int"
        //repeatCount设置-1时才会有效果
        android:repeatMode=["repeat" | "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=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

下面列出了常见的属性名字,另外需要注意的是,使用属性动画时,必须有相应属性的set/get方法,否则属性动画没有效果的。

  • translationX 和 translationY : 控制View距离左边和顶部的距离的增加值。是一个相对值。相对于自身位置的具体。

  • rotation 、 rotationX 和 rotationY : rotation 是控制View围绕其支点进行旋转。 rotationX 和 rotationY 分别是围绕X轴和Y轴旋转。

  • scaleX 和 scaleY : 控制View的缩放。

  • pivotX 和 pivotY : 控制View的支点位置,进行旋转和缩放,默认是View的中点。它们都是 float 值, 0 表示View的最左边和最顶端, 1 表示最右端和最下端。

  • alpha : 控制View的透明度。

  • x 和 y : 控制View在布局容器中距离左边和顶部的距离。是一个绝对值。

例如我们实现一个旋转加透明度变化的动画,效果图如下


frame1.gif
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">
    <objectAnimator
        android:duration="500"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="180" />
    <objectAnimator
        android:duration="500"
        android:propertyName="alpha"
        android:valueFrom="1.0f"
        android:valueTo="0.5f" />
</set>

然后

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,
                R.animator.property_animator);
set.setTarget(imageView);
set.start();

当然不用xml书写也是很简单的,如下代码

      ObjectAnimator.ofFloat(imageView,"rotation",0,180,90,180)
                .setDuration(2000).start();

代码实现的效果就是在2秒内先旋转到180度,在回到90度在转回180度
效果图如

frame1.gif

在上面代码实现了一直属性动画,那么如果我们想同时作用几个属性那该如何操作呢。此时我们有两种实现方式分别是类PropertyValuesHolder和AnimatorSet,话不多说,先上图再直接上代码。

frame1.gif
  private void startPropertyAnimation3() {
        PropertyValuesHolder translationX = PropertyValuesHolder
                .ofFloat("translationX", -200, -100, 100, 200, 300);
        PropertyValuesHolder scaleX = PropertyValuesHolder
                .ofFloat("scaleX", 1.0f, 2.0f);
        PropertyValuesHolder rotate = PropertyValuesHolder
                .ofFloat("rotation", 0f, 360f);
        PropertyValuesHolder rotationX = PropertyValuesHolder
                .ofFloat("rotationX", 0f, 180f);
        ObjectAnimator together = ObjectAnimator
                .ofPropertyValuesHolder(imageView, translationX, rotate, scaleX, rotationX);
        together.setDuration(3000);
        together.start();
    }

    //或者使用AnimatorSet,此方法使用的是按顺序播放。
    private void startPropertyAnimation4() {
        ObjectAnimator translationX = ObjectAnimator
                .ofFloat(imageView, "translationX", -200, -100, 100, 200, 300);
        ObjectAnimator scaleX = ObjectAnimator
                .ofFloat(imageView, "scaleX", 1.0f, 2.0f)
                .setDuration(1000);
        ObjectAnimator rotation = ObjectAnimator
                .ofFloat(imageView, "rotation", 0f, 360f)
                .setDuration(1000);
        ObjectAnimator rotationX = ObjectAnimator
                .ofFloat(imageView, "rotationX", 0f, 180f)
                .setDuration(1000);
        AnimatorSet set = new AnimatorSet();
        set.playSequentially(translationX, scaleX, rotation, rotationX);
        set.setDuration(4000);
        set.start();
    }

Fragment/Activity动画

其实实现Activity及Fragment切换动画也是很简单的,具体的动画效果制作可以使用即使上面介绍的补间动画。例如我们Fragment动画。

FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
transaction.replace(R.id.fragment_container, fragment, fragmentTag);
transaction.commit();

Activity实现动画也很简单,在Activity中提供了overridePendingTransition(int enterAnim, int exitAnim) 方法,该方法接收两个参数,第一个参数是Activity进入时的动画,第二个参数是Activity退出时的动画。该方法一般写在startActivity()后和finish()后,如果我们想打开或者退出不显示动画,可将参数设置为0。

在上面的我们介绍了Activity/Fragment在代码中实现动画的方法,当然还有一种简单的实现方式,那就是在主题中设置动画。
对于Activity

    <style name="CustomeActivityStyle" parent="AppTheme">
        <item name="android:windowAnimationStyle">@style/AnimationStyle</item>
    </style>
    <style name="AnimationStyle">
        <item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
        <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
        <item name="android:activityOpenEnterAnimation">@anim/slide_in_left</item>
        <item name="android:activityOpenExitAnimation">@anim/slide_out_right</item>
    </style>

可能你不太理解为什么是设置了四种,假如有Activity1和Activity2。当我们在Activity1中跳转到Activity2时,Activity1在页面上消失是执行的:activityOpenExitAnimation动画,Activity2出现在屏幕上执行的动画是activityOpenEnterAnimation。当Activity2 finish返回Activity1时,Activity2执行的动画是activityCloseExitAnimation,Activity1显示在屏幕执行的是activityCloseEnterAnimation。创建上面主题后我们需要将该主题应用到我们的Activty中就可以了。

同理Fragment也可相应设置,如activityCloseEnterAnimation改为fragmentCloseEnterAnimation即可。
除此之外我们也可以使用windowAnimation,它包括 windowEnterAnimation 和 windowExitAnimation。注意的一点就是WindowAnimation的控制权大于Activity/Fragment Animation的控制权。

除了上面介绍的动画实现,还有一些动画是从Android5.0增加的,你可以参考文末给出的链接文章,酷炫的Activity切换动画,打造更好的用户体验。个人感觉这篇文章介绍的挺详细。对本文缺少的内容也做了一个补充。到这里该篇文章就暂时告一段落,有问题欢迎指出,Have a wonderful day.

定义定制动画
酷炫的Activity切换动画,打造更好的用户体验

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

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,651评论 0 10
  • 转载一篇高质量博文,原地址请戳这里转载下来方便今后查看。1 背景不能只分析源码呀,分析的同时也要整理归纳基础知识,...
    Elder阅读 1,932评论 0 24
  • 1.定义# 定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法...
    tdeblog阅读 274评论 0 0
  • 这次核心转换有体验到什么是真正的核心状态,第一个是用了我自己会一直纠结自己上台的表现好还是不好?就像昨天上完台后,...
    甜心教主阅读 250评论 0 0
  • 数据清洗: 数据导出 数据录入、变量标签修改、变量属性修改 数据异常值删除(箱线图)、空值补齐或删除 1.数据导出...
    小豆角lch阅读 2,118评论 0 0