Android开发总结之动画(帧动画+补间动画)

一、概述

  1. 动画的概念
      动画的概念不同于一般意义上的动画片,动画是一种综合艺术,它是集合了绘画、漫画、电影、数字媒体、摄影、音乐、文学等众多艺术门类于一身的艺术表现形式。
      动画的英文有很多表述,如animation、cartoon、animated cartoon、cameracature。其中较正式的 "Animation" 一词源自于拉丁文字根anima,意思为“灵魂”,动词animate是“赋予生命”的意思,引申为使某物活起来的意思。所以动画可以定义为使用绘画的手法,创造生命运动的艺术。
      动画技术较规范的定义是采用逐帧拍摄对象并连续播放而形成运动的影像技术。不论拍摄对象是什么,只要它的拍摄方式是采用的逐格方式,观看时连续播放形成了活动影像,它就是动画。

  2. Android系统中的动画
      在Android系统中,动画可分为三类,分别为帧动画(Frame Animation),补间动画(Tweened Animation),属性动画。本章主要讲述帧动画和补间动画。

二、动画的实现

  1. 帧动画
      帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。其表现出来的样式类似于我们常见到的GIF图。而帧动画所呈现的结果也依赖于每一“帧”,大家先看效果图:
    帧动画效果

      这个奔跑的京东小人以及跳跃的鱼,其实是一张张不同的图片很快的切换而形成的动画效果。看我的资源文件:
    资源文件

      这么多图片资源的切换,内存开销~~~
    1.1 我们来看看帧动画的实现,首先上代码:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@mipmap/a_0"
        android:duration="100" />
    <item
        android:drawable="@mipmap/a_1"
        android:duration="100" />
    <item
        android:drawable="@mipmap/a_2"
        android:duration="100" />
</animation-list>

  这是在res/drawable目录下建立一个的animation-list标签的动画文件,每个item里面有一张图,多张图组合起来就是我们的动画了。而android:duration则是表示每张图呈现的时间长短。还有一个android:oneshot属性,代表是否只展示一遍,true的话该动画只会执行一遍,false则会循环播放动画。
1.2 Activity里面的代码:

public class FrameActivity extends AppCompatActivity {
    private SimpleDraweeView mImgvOne;
    private ImageView mImgvTwo;
    private AnimationDrawable animationDrawableOne;
    private AnimationDrawable animationDrawableTwo;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame);
        mImgvOne = (SimpleDraweeView) findViewById(R.id.imgv_one);
        mImgvTwo = (ImageView) findViewById(R.id.imgv_two);
        setAnimation();

    }

    private void setAnimation() {
        //Fresco的动画 和ImageView用法一样
        mImgvOne.setImageResource(R.drawable.fram_one);
        animationDrawableOne = (AnimationDrawable) mImgvOne.getDrawable();
        animationDrawableOne.start();
        //ImageView的动画
        mImgvTwo.setImageResource(R.drawable.fram_two);
        animationDrawableTwo = (AnimationDrawable) mImgvTwo.getDrawable();
        animationDrawableTwo.start();
    }
}

  java代码很简单,导入动画开始动画,三行代码就可。大家可以看到我的mImgvOne是一个SimpleDraweeView,因为现在很多人使用Fresco,我只是想证明在使用动画时SimpleDraweeView的用法和ImageView一样,大家不必担心用了Fresco就没法玩动画了。而且基于Fresco的强大基因,想要实现帧动画?人家Fresco是支持gif图的,直接往里面放一个gif图,比我们设置帧动画简单多了。
  下面我们再看看AnimationDrawable这个类,我们在执行帧动画时要依靠AnimationDrawablestart方法,先上源码分析一下:

public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
    public AnimationDrawable() {
        throw new RuntimeException("Stub!");
    }

    public boolean setVisible(boolean visible, boolean restart) {
        throw new RuntimeException("Stub!");
    }

    public void start() {
        throw new RuntimeException("Stub!");
    }

    public void stop() {
        throw new RuntimeException("Stub!");
    }

    public boolean isRunning() {
        throw new RuntimeException("Stub!");
    }

    public void run() {
        throw new RuntimeException("Stub!");
    }

    public void unscheduleSelf(Runnable what) {
        throw new RuntimeException("Stub!");
    }

    public int getNumberOfFrames() {
        throw new RuntimeException("Stub!");
    }

    public Drawable getFrame(int index) {
        throw new RuntimeException("Stub!");
    }

    public int getDuration(int i) {
        throw new RuntimeException("Stub!");
    }

    public boolean isOneShot() {
        throw new RuntimeException("Stub!");
    }

    public void setOneShot(boolean oneShot) {
        throw new RuntimeException("Stub!");
    }

    public void addFrame(Drawable frame, int duration) {
        throw new RuntimeException("Stub!");
    }

    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException {
        throw new RuntimeException("Stub!");
    }

    public Drawable mutate() {
        throw new RuntimeException("Stub!");
    }

    protected void setConstantState(DrawableContainerState state) {
        throw new RuntimeException("Stub!");
    }
}
方法 作用
boolean setVisible(boolean visible, boolean restart) visible表示AnimationDrawable是否可见,false则暂停当前动画运行。restart的值true表示当AnimationDrawable设为Visible时,从第一帧开始播放动画,false表示当AnimationDrawable设为Visible时,从最近的帧开始执行动画。
void start() 顾名思义,开始动画执行。
void stop() 与上面方法相反,停止动画执行。
boolean isRunning() 当前动画是否在执行。
void unscheduleSelf(Runnable what) 取消当前动画上计划执行的一个Runnable,一般这个Runnable都是用于绘制下一帧的。
int getNumberOfFrames() 获取帧数(图片数)
Drawable getFrame(int index) 获取指定帧
int getDuration(int i) 获取指定帧的展示时长
boolean isOneShot() 是否循环,true则不循环,false则循环
void setOneShot(boolean oneShot) 设置是否循环,true or false
void addFrame(Drawable frame, int duration) 增加一帧图片

  可以看到AnimationDrawable继承了DrawableContainer,而DrawableContainer其实就是Drawable的一个子类,这也是我们animationDrawableTwo = (AnimationDrawable) mImgvTwo.getDrawable();Drawable强转成AnimationDrawable的原因了。所以AnimationDrawable也是个Drawable罢了。再看它所实现的接口Runnable, Animatable,表明它是一个可执行命令,并且自己支持动画。源码不难,下面简略介绍一下它的相关方法:

方法 作用
boolean setVisible(boolean visible, boolean restart) visible表示AnimationDrawable是否可见,false则暂停当前动画运行。restart的值true表示当AnimationDrawable设为Visible时,从第一帧开始播放动画,false表示当AnimationDrawable设为Visible时,从最近的帧开始执行动画。
void start() 顾名思义,开始动画执行。
void stop() 与上面方法相反,停止动画执行。
boolean isRunning() 当前动画是否在执行。
void unscheduleSelf(Runnable what) 取消当前动画上计划执行的一个Runnable,一般这个Runnable都是用于绘制下一帧的。
int getNumberOfFrames() 获取帧数(图片数)
Drawable getFrame(int index) 获取指定帧
int getDuration(int i) 获取指定帧的展示时长
boolean isOneShot() 是否循环,true则不循环,false则循环
void setOneShot(boolean oneShot) 设置是否循环,true or false
void addFrame(Drawable frame, int duration) 增加一帧图片

  源码解析完了,我们也看到了AnimationDrawable的一些方法。下面我们再看看不通过xml文件,直接在代码中执行帧动画的方法。先上代码:

        animationDrawableOne = new AnimationDrawable();
        animationDrawableOne.addFrame(getResources().getDrawable(R.mipmap.a_0),100);
        animationDrawableOne.addFrame(getResources().getDrawable(R.mipmap.a_1),100);
        animationDrawableOne.addFrame(getResources().getDrawable(R.mipmap.a_2),100);
        animationDrawableOne.setOneShot(false);
        mImgvOne.setBackground(animationDrawableOne);
        animationDrawableOne.start();

  同样,使用代码依旧可以做出帧动画的效果,所以大家是使用xml还是代码创建动画,看大家喜好咯。

  1. 补间动画(Tween)
      补间动画指的是做flash动画时,在两个关键帧中间需要做“补间动画”,才能实现图画的运动;插入补间动画后两个关键帧之间的插补帧是由计算机自动运算而得到的。也就是说在使用补间动画时,我们开发者指定了动画开始、结束的关键帧,中间的变化是计算机自动帮助我们补齐的。
      补间动画有四种基本形式,分别是Alpha(透明度),Translate(位移),Scale(缩放),Rotate(旋转)。当然还可以是多种动画效果的组合,例如alpha+translatealpha+rotate,甚至四种形式组合在一起的动画。同样,和帧动画一样,补间动画的可以以xml的方式实现,也可以以代码的方式实现。
    2.1 补间动画的使用
       属性解释:
    | JAVA方法 | xml属性 | 解释 |
    |:--------|:------|:----|
    |setDetachWallpaper(boolean)| android:detachWallpaper |是否在壁纸上运行|
    |setDuration(long) |android:duration |设置动画持续时间,单位为毫秒|
    |setFillAfter(boolean) |android:fillAfter |控件动画结束时控件是否保持动画最后状态|
    |setFillBefore(boolean) |android:fillBefore |控件动画结束时控件是否还原到开始动画前的状态|
    |setFillEnable(boolean)| android:fillEnable(boolean)| 与android:fillBefore效果相同|
    |setInterpolator(boolean) |android:interpolator |设置插值器,有一些动画效果,如从快到慢,从慢到快,也可以自定义)|
    |setRepeatCount(int) |android:repeatCount |重复次数|
    |setRepeatMode(int)| android:repeatMode |重复类型:reverse倒序回放、restart从头播放|
    |setStartOffset(long) |android:startOffset |调用start函数后等待开行运行的时间,单位为毫秒|
    |setZadjustment(int) |android:zAdjustment |表示被设置动画的内容运行时在Z轴的位置(top/bottom/normal),默认为normal|
    |Alpha|
    |AlphaAnimation(float fromAlpha, float toAlpha)|android:fromAlpha|起始透明度|
    |AlphaAnimation(
    float fromAlpha, float toAlpha)| android:toAlpha|结束透明度|
    |Rotate|||
    |RotateAnimation(float fromDegrees, *float toDegrees, *float pivotX, float pivotY) |android:fromDegress |旋转开始角度,正代表顺时针度数,负代表逆时针度数|
    |RotateAnimation(
    float fromDegrees, float toDegrees, *float pivotX, float pivotY)| android:toDegress| 旋转结束角度(同上)|
    |RotateAnimation(
    float fromDegrees, *float toDegrees, float pivotX, float pivotY)|android:pivotX |缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)|
    |RotateAnimation(
    float fromDegrees, *float toDegrees, float pivotX, float pivotY)|android:pivotY|缩放起点Y坐标(同上)|
    |Scale|||
    |ScaleAnimation(float fromX,
    float toX, *float fromY, *float toY, *float pivotX, float pivotY) |android:fromXScale |初始X轴缩放比例,1.0表示无变化|
    |ScaleAnimation(
    float fromX, float toX, *float fromY, *float toY, *float pivotX, float pivotY)|android:toXScale |结束X轴缩放比例|
    |ScaleAnimation(
    float fromX, *float toX, float fromY, *float toY, *float pivotX, float pivotY)| androd:fromYScale |初始Y轴缩放比例|
    |ScaleAnimation(f
    loat fromX, *float toX, *float fromY, float toY, *float pivotX, float pivotY)| android:toYScale| 结束Y轴缩放比例|
    |ScaleAnimation(
    float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)| android:pivotX |缩放起点X轴坐标(同上)|
    |ScaleAnimation(
    float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)|android:pivotY |缩放起点Y轴坐标(同上)|
    |Translate|||
    |TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)| android:fromXDelta |平移起始点X轴坐标|
    |TranslateAnimation(
    float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)|android:toXDelta |平移结束点X轴坐标|
    |TranslateAnimation(
    float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) |android:fromYDelta| 平移起始点Y轴坐标|
    |TranslateAnimation(
    float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)| android:toYDelta |平移结束点Y轴坐标|
    注:
    为忽略该属性,例如
    |JAVA方法|xml属性|解释|
    |---|---|---|
    |AlphaAnimation(float fromAlpha, float toAlpha)|android:fromAlpha|起始透明度|
    |AlphaAnimation(
    float fromAlpha, float toAlpha)| android:toAlpha|结束透明度|
      第一行意为只解释float fromAlpha属性,第二行意为只解释float toAlpha属性。
      实现Alpha效果,首先,在
    res
    目录下新建一个
    anim
    文件夹用来存储动画文件,然后新建一个
    Animation resource file
    xml
    文件,我的代码里命名其为
    alpha_one
    ,项目地址在文末,大家可以下载参考一下。上面已经总结了各种属性,所以我们直接看代码。先是
    xml
    文件:
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    />

  然后Activity中代码:

 animation = AnimationUtils.loadAnimation(this, R.anim.alpha_one);
 mImgvTween.startAnimation(animation);

  使用AnimationUtils装载动画配置文件。两行代码,即可实现如下效果:

Alpha效果

  不清楚anim文件夹建在哪里的同学请看我的目录结构:
目录结果

  我们再看看不通过xml文件,纯代码实现补间动画的方法。

  AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
  anim.setDuration(3000);
  mImgvTween.startAnimation(anim);

  如果要实现其它旋转、缩放、位移等效果,使用不同的Animation例如 RotateAnimation、 ScaleAnimation、TranslateAnimation,根据传入不同的参数实现我们想要的效果。当然也可以使用xml文件的方式,代码很简单,大家可以看我传到github上的代码。
  动画组合:
  我们可以使用动画组合把多个动画效果结合到一起展示。如果我们要实现一个缩放和透明度同时变化的效果,像这样:

动画组合

  和其它效果一样,我们仍然有xml文件和纯java代码实现的两种实现方法。如果使用xml方式的话,按照之前的步骤,在Animation resource file创建个节点为setxml文件即可。先上xml实现的代码:

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="2000"
        android:fromAlpha="0.0"
               android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:toAlpha="1.0" />
    <scale
        android:duration="2000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale="1.0"
        android:toYScale="1.0" />
</set>

  Activity中的代码:

 animation = AnimationUtils.loadAnimation(this, R.anim.set_one);
 mImgvTween.startAnimation(animation);

  从这里可以看出,我们若使用xml方式实现补间动画,无论要实现哪一种动画效果或是组合效果,其java代码是相同的,只是xml文件中的节点不同而已。
  我们再看看纯代码的动画组合效果实现,上代码:

                AnimationSet animationSet = new AnimationSet(this,null);
                AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f,1.0f);
                alphaAnimation.setDuration(2000);
                animationSet.addAnimation(alphaAnimation);
                ScaleAnimation scaleAnimation = new ScaleAnimation(0.0f,1.0f,0.0f,1.0f,Animation.RELATIVE_TO_PARENT,0.5f,Animation.RELATIVE_TO_PARENT,0.5f);
                scaleAnimation.setDuration(2000);
                animationSet.addAnimation(scaleAnimation);
                mImgvTween.startAnimation(animationSet);

  这种方式最终效果和xml方式完全一样。所以到底用哪种方法,也是个人喜好咯。

三、总结

  动画的可玩性还是很高的,我们可以用帧动画实现下拉刷新的刷新头动图效果(如京东,美团),也可以用补间动画实现Dialog进出场动画效果。通过不同的动画组合,我们可以大大提升我们app的美观程度。
github项目地址:https://github.com/tangxuesong6/animation

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

推荐阅读更多精彩内容