一、概述
动画的概念
动画的概念不同于一般意义上的动画片,动画是一种综合艺术,它是集合了绘画、漫画、电影、数字媒体、摄影、音乐、文学等众多艺术门类于一身的艺术表现形式。
动画的英文有很多表述,如animation、cartoon、animated cartoon、cameracature。其中较正式的 "Animation" 一词源自于拉丁文字根anima,意思为“灵魂”,动词animate是“赋予生命”的意思,引申为使某物活起来的意思。所以动画可以定义为使用绘画的手法,创造生命运动的艺术。
动画技术较规范的定义是采用逐帧拍摄对象并连续播放而形成运动的影像技术。不论拍摄对象是什么,只要它的拍摄方式是采用的逐格方式,观看时连续播放形成了活动影像,它就是动画。Android系统中的动画
在Android系统中,动画可分为三类,分别为帧动画(Frame Animation),补间动画(Tweened Animation),属性动画。本章主要讲述帧动画和补间动画。
二、动画的实现
- 帧动画
帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。其表现出来的样式类似于我们常见到的GIF图。而帧动画所呈现的结果也依赖于每一“帧”,大家先看效果图:
这个奔跑的京东小人以及跳跃的鱼,其实是一张张不同的图片很快的切换而形成的动画效果。看我的资源文件:
这么多图片资源的切换,内存开销~~~
1.1 我们来看看帧动画的实现,首先上代码:
这是在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
这个类,我们在执行帧动画时要依靠AnimationDrawable
的start
方法,先上源码分析一下:
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
还是代码创建动画,看大家喜好咯。
- 补间动画(Tween)
补间动画指的是做flash动画时,在两个关键帧中间需要做“补间动画”,才能实现图画的运动;插入补间动画后两个关键帧之间的插补帧是由计算机自动运算而得到的。也就是说在使用补间动画时,我们开发者指定了动画开始、结束的关键帧,中间的变化是计算机自动帮助我们补齐的。
补间动画有四种基本形式,分别是Alpha(透明度),Translate(位移),Scale(缩放),Rotate(旋转)。当然还可以是多种动画效果的组合,例如alpha+translate、alpha+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(float 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文件:
然后Activity中代码:
animation = AnimationUtils.loadAnimation(this, R.anim.alpha_one);
mImgvTween.startAnimation(animation);
使用AnimationUtils装载动画配置文件。两行代码,即可实现如下效果:
不清楚 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创建个节点为
set
的
xml文件即可。先上
xml实现的代码:
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