欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android动画总结系列(1)——帧动画

程序员文章站 2024-03-24 15:05:16
...
一、综述

帧动画(Frame Animation,又叫Drawable Animation)是最简单的Android动画效果,其模仿的是电影的多重连续帧播放策略,通过视觉残留来让人感知到动画效果。
帧动画将一张张Drawable按顺序排列,并逐张按时播放来实现动画效果。其对应的Android类是AnimationDrawable。
帧动画有两种实现方式:纯代码实现和XML实现,XML实现相对更简单

二、xml用法

2.1 动画定义

将动画XML文件定义在 res/drawable/ 目录下,其中的条目是每一帧的顺序和展示时间。XML文件的根元素是<animation-list>节点,其子元素是一个个的<item>节点,每一个item节点定义一个帧,每个帧中包含其需要展示的Drawable以及其展示时间。用法如下:
res/drawable/frame_animation_demo.xml定义
<animation-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    android :oneshot="false">
    <item android:drawable="@color/color_red" android:duration="200"/>
    <item android:drawable="@color/color_green" android:duration="200"/>
    <item android:drawable="@color/color_blue" android:duration="200"/>
</animation-list >

drawable可以是任何drawable对象,包括:drawable-xxx下面的所有bitmap、values下定义的color等;此处没用普通的位图而是使用颜色,主要是因为写demo不用弄太多切片,实际应用中这里基本上都是位图切片。

oneshot属性true表示动画只执行一次,执行完成后停在最后一帧;false表示动画无限循环

animation-list还有两个自定义属性:
android:visible 参数为布尔值,设置AnimationDrawable的可见性,true可见,false不可见,xml中定义的visible属性无用,因为根本没有解析。
android:variablePadding 表示是否支持可变的Padding。false表示使用所有帧中最大的Padding,true表示使用当前帧的padding。

注意:此处item还可以嵌套子元素,但是其子元素只能被解析出一个Drawable,如:
<item android:drawable="@color/color_blue" android:duration="200"/>后有一个item为:
<item android:duration= "1000">
    <animation-list >
        <item android:drawable="@color/color_red" android:duration="500"/>
        <item android:drawable="@color/color_green" android:duration="500"/>
    </animation-list >
</item>

此时外层item的drawable为color_red,相当于外层item从内层aniamtion-list的子元素内找到一个drawable用做自己的drawable。

2.2 代码集成

帧动画可以作为Drawable展示在任何View的背景或其他属性(如ImageView的src)上。通过代码获取到drawable对象强转成AnimationDrawable即可开始或结束动画。
<ImageView
    android :layout_width="match_parent"
    android :layout_height="100dp"
    android :gravity="center"
    android:src="@drawable/frame_animation_demo"/>

<TextView
    android :text="帧动画"
    android :layout_width="match_parent"
    android :layout_height="200dp"
    android :gravity="center"
    android:background="@drawable/frame_animation_demo"/>

取动画对象
AnimationDrawable animationDrawable = (AnimationDrawable) mTxtView .getBackground();
AnimationDrawable animationDrawable = (AnimationDrawable) mImgView.getDrawable();

开始动画
if(!animationDrawableBg.isRunning()) {
    animationDrawableBg.start();
}

start方法不能在onCreate方法内调用,因为此时AnimationDrawable还未绘制(attach)到界面上,如果需要进入界面就自动开始动画,需要在onWindowFocusChanged()回调中执行,此时界面已经创建完成。

结束动画
if(animationDrawableBg.isRunning()) {
    animationDrawableBg.stop();
}

三、代码实现

3.1 动画实现

AnimationDrawable  mAnimationDrawableBg = new AnimationDrawable();
mAnimationDrawableBg .addFrame(getResources().getDrawable(R.color.color_red), 200);
mAnimationDrawableBg .addFrame(getResources().getDrawable(R.color.color_green), 200);
mAnimationDrawableBg .addFrame(getResources().getDrawable(R.color.color_blue), 200);
mAnimationDrawableBg .setOneShot(false);

此代码与上文xml定义完全等价。

对应xml中visible、variablePadding的API是:
boolean setVisible( boolean visible, boolean restart):visible表示AnimationDrawable是否可见,false则暂停当前动画运行。restart的值true表示当AnimationDrawable设为Visible时,从第一帧开始播放动画,false表示当AnimationDrawable设为Visible时,从最近的帧开始执行动画。

DrawableContainerState的setVariablePadding( boolean variable ) 该接口表示是否支持可变的Padding。false表示使用所有帧中最大的Padding,true表示使用当前帧的padding。

3.2 代码集成

3.2.1 给TextView设背景,并让背景动画执行
mTxtViewFrameAnimationContainer .setBackgroundDrawable(mAnimationDrawableBg);
if(!mAnimationDrawableBg .isRunning()) {
    mAnimationDrawableBg .start();
}

结束背景动画执行:
if(mAnimationDrawableBg .isRunning()) {
    mAnimationDrawableBg .stop();
}

3.2.2 给ImageView设前景,并让前景动画执行
mImgViewFrameAnimationContainer .setImageDrawable(mAnimationDrawableSrc);
if(!mAnimationDrawableSrc .isRunning()) {
    mAnimationDrawableSrc .start();
}

结束前景动画执行
if(mAnimationDrawableSrc .isRunning()) {
    mAnimationDrawableSrc .stop();
}

3.2.3 如果同一个动画既给TextView做背景,又给ImageView做前景,这时候调用start方法会出现什么情况呢?
mImgViewFrameAnimationContainer .setImageDrawable(mAnimationDrawableBg);
mTxtViewFrameAnimationContainer .setBackgroundDrawable(mAnimationDrawableBg);
if(!mAnimationDrawableBg .isRunning()) {
    mAnimationDrawableBg .start();
}
这时候ImageView是红色的,TextView执行动画效果。

mTxtViewFrameAnimationContainer .setBackgroundDrawable(mAnimationDrawableBg);
mImgViewFrameAnimationContainer .setImageDrawable(mAnimationDrawableBg);
这时候TextView背景红色,ImageView执行动画效果。

也就是说,同一个AnimationDrawable对象的start只会在最后一个应用它的View上生效。

四、AnimationDrawable类分析

如前所述,帧动画对应的Android类是AnimationDrawable。下面来简要介绍下其相关API。
AnimationDrawable继承DrawableContainer,并实现Runnable(表明自己是一个可执行命令), Animatable(表明自己支持动画接口)接口。其主要接口有:
1)boolean setVisible( boolean visible, boolean restart): 作用前面已经说明了,其返回值表示新的可见性与之前状态不同;
2)void start():开始动画执行,如果动画正在执行,此方法无效;不能在onCreate调用,如果有必要界面启动就运行动画,则在onWindowFocusChanged中调用;
3)void stop():停止动画执行,如果动画不再执行,则此方法无效;
4)boolean isRunning():返回动画是否正在执行;
5)void unscheduleSelf(Runnable what):取消当前动画上计划执行的一个Runnable,一般这个Runnable都是用于绘制下一帧的;
6)int getNumberOfFrames():获取当前动画的帧数量;
7)Drawable getFrame( int index):获取指定位置的帧;
8)int getDuration( int i):获取指定位置帧的展示时长;
9)boolean isOneShot():获取动画是执行性一次还是无限循环,true只执行一次,false无限循环;
10)void setOneShot( boolean oneShot):设置动画是执行一次还是无限循环;
11)void addFrame(@NonNull Drawable frame, int duration):添加一个帧到动画序列中;
12)Drawable mutate():
13)void clearMutated():

布局相关公有方法:
12)void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme):从xml中取得VariablePadding、OneShot设置到AnimationState中(见updateStateFromTypedArray)。

关键私有方法
1)void nextFrame( boolean unschedule):跳转到下一帧,unschedule值true表示要取消当前帧的runnable注册,false表示不取消当前帧在runnable中的注册;runnable执行时run()方法不断的调用此方法,刷新动画展示;
2)void setFrame(int frame, boolean unschedule, boolean animate):设置当前帧;frame表示当前帧的位置;unschedule如上,animate表示是否继续执行动画,true不执行,fasle执行;
此方法比较重要:着重分析下:

//异常处理
if (frame >= mAnimationState.getChildCount()) {
    return ;
}
//更新当前正在执行动画标志位的状态
mAnimating = animate;
//更新当前帧
mCurFrame = frame;
//设置当前帧
selectDrawable(frame);
//如果需要停止当前Drawable的动画执行或者动画需要继续执行,都将此Runnable取消注册
if (unschedule || animate) {
    unscheduleSelf(this );
}
//重新注册下一帧动画,下一帧动画在run()中执行,最终执行到nextFrame,nextFrame加一帧后接着执行setFrame
if (animate) {
    // Unscheduling may have clobbered these values; restore them
    mCurFrame = frame;
    mRunning = true;
    scheduleSelf(this , SystemClock.uptimeMillis() + mAnimationState.mDurations [frame]);
}

3)void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme):解析XML中定义的animation-list的帧

内部类
AnimationState:存储动画的各种属性,包括各帧及其展示时间,是否循环执行,padding是否可变等。

整体来看,AnimationDrawable的本质就是在指定时间调用setFrame方法不断的更新当前帧,从而形成动画效果。

五、兼容性

帧动画从Android1.0版本开始支持,集成过程中未发现兼容性问题