一、前言
今天的内容主要是对动画做一次简单的介绍,由于平时项目里用到动画的地方很少,即使每天都在用的加载等待等动画,也是直接从网上搜下demo就算了事儿的状态,一直也没有对这块做一次系统的总结,所以这也就造成了现在对于一些复杂的动画有一种害怕心理,拿到了一个需求却无从下手,这种感觉真的很不爽,所以打算看下动画这个模块,从基础开始慢慢的深入研究一下。本篇由于考虑到篇幅会太长的问题,所以只是介绍一下逐帧动画和补间动画的基本用法,属性动画以及一些深入的知识点都会在其它的文章里再做介绍。
二、逐帧动画
在开始介绍逐帧动画之前,先来看一下在本例中我们要实现的逐帧动画的效果
逐帧动画,我的理解就是将一帧帧的静态图片进行有序的展示,利用人眼的视觉暂留现象来呈现给用户一种连贯的动画效果。在实际编写代码过程中,我们主要是通过系统提供的AnimationDrawable类来控制逐帧动画,AnimationDrawable又是何许类也?这是API中给我们的介绍
通过API介绍我们知道,AnimationDrawable类一种用于创建逐帧动画的对象,由一系列可绘制对象定义,该对象可以用作视图对象的背景。创建逐帧动画最简单的方法是在XML文件中定义动画,下面来让我们看下具体的实现吧。
通过xml定义动画
将我们所需要的图片放入mipmap文件夹下,当前动画即由这6帧组成
在drawable文件夹下,新建一个anim_frame_load.xml文件,现在Android Studio只支持将animation-list的xml文件放在drawable目录之下
//res右键=>Android Resource File=>File name:anim_frame_load(xml文件名字),Resource type:Drawable,Root element:animation-list,Directory name:drawable(文件夹的名字)
drawable目录结构如下所示
在anim_frame_load.xml文件中编写animation-list,并可以设置属性android:id=”selected”和设置是否只播放一次android:oneshot=”false”
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@mipmap/load01" android:duration="200"></item>
<item android:drawable="@mipmap/load02" android:duration="200"></item>
<item android:drawable="@mipmap/load03" android:duration="200"></item>
<item android:drawable="@mipmap/load04" android:duration="200"></item>
<item android:drawable="@mipmap/load05" android:duration="200"></item>
<item android:drawable="@mipmap/load06" android:duration="200"></item>
</animation-list>
这是我们的帧动画类,通过AnimationDrawable来执行动画,代码比较简单
public class FrameAnimationTest extends Activity{
//动画对象
AnimationDrawable animDrawable;
//添加动画的控件
ImageView imgLoad;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.anim_frame_load);
initView();
addClickListener();
}
private void initView() {
imgLoad = (ImageView) findViewById(R.id.img_load);
}
private void addClickListener() {
findViewById(R.id.tv_load_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//开始逐帧动画
startAnimByXml();
}
});
findViewById(R.id.tv_load_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//结束动画
stopAnim();
}
});
}
/** xml方式开始动画*/
private void startAnimByXml() {
imgLoad.setBackgroundResource(R.drawable.anim_frame_load);
animDrawable = (AnimationDrawable) imgLoad.getBackground();
//如果动画正在运行,则此方法没有效果,所以我们不必判断动画状态了
animDrawable.start();
}
/** 结束动画*/
private void stopAnim() {
if(animDrawable != null){
//如果动画不运行,此方法没有效果,所以我们不必判断动画状态了
animDrawable.stop();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(animDrawable != null){
//当界面销毁的时候,销毁animDrawable
animDrawable = null;
}
}
}
其实帧动画不仅可以通过xml方式定义,还能在代码中直接实现,因为AnimationDrawable类为我们提供了addFrame(Drawable frame, int duration) 这个直接添加帧图片Drawable对象到动画中的方法,同时它还提供了一系列设置动画属性和获取状态的公开方法,下面来让我们看看具体的实现。
直接在代码中实现
将图片添加到项目mipmap目录下
我们直接在代码中通过AnimationDrawable来实现帧动画,所以就不必编写xml文件了,这种方式比较适用于你的项目动画需要动态扩展或者项目对于动画的灵活性有很高的要求,而如果你更加看重的是动画的高度复用性,那么就优先选择xml定义动画的方式吧,毕竟这也是API所推荐的方式。
public class FrameAnimationTest extends Activity{
//当使用java code实现的时,动画图片资源数组
int[] anims = {R.mipmap.load01
, R.mipmap.load02
, R.mipmap.load03
, R.mipmap.load04
, R.mipmap.load05
, R.mipmap.load06};
//动画对象
AnimationDrawable animDrawable;
//添加动画的控件
ImageView imgLoad;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.anim_frame_load);
initView();
addClickListener();
}
private void initView() {
imgLoad = (ImageView) findViewById(R.id.img_load);
}
private void addClickListener() {
//开始逐帧动画
findViewById(R.id.tv_load_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
statrAnimByJavaCode();
}
});
//结束动画
findViewById(R.id.tv_load_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopAnim();
}
});
}
/** java code方式开始动画*/
private void statrAnimByJavaCode() {
if(animDrawable == null){
animDrawable = new AnimationDrawable();
}
for(int source : anims) {
//添加每帧图片资源和设置每帧持续时间
animDrawable.addFrame(getResources().getDrawable(source), 200);
}
animDrawable.setOneShot(false);//循环播放
imgLoad.setBackground(animDrawable);
animDrawable.start();
}
/** 结束动画*/
private void stopAnim() {
if(animDrawable != null){
animDrawable.stop();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(animDrawable != null){
animDrawable = null;
}
}
}
由于我们将AnimationDrawable对象的创建和动画的启动,都放在了点击监听方法onClick里,所以目前开始动画之前,界面是空白的,不会显示动画的第一帧图片,如果你有这方面的需求,解决的最快方法就是直接在layout文件里给view直接加上background
<!--这是我们要添加动画的View-->
<ImageView
android:id="@+id/img_load"
android:layout_width="68dp"
android:layout_height="68dp"
android:layout_centerInParent="true"
android:background="@drawable/anim_frame_load"
/>
好了,到这里帧动画的基本使用我们心里应该都有底了,但是对于源码层次的原理剖析,或者帧图片过大或者帧数量过多等具体情况的优化在这里都没有涉及,如果你想好好研究动画,那么就去一点点的深入吧,我在接下来的文章中也会再写几篇关于动画的总结,下面我们来看下补间动画的基本使用。
三、补间动画
Android的补间动画主要分为4种,即translate(位移)、scale(缩放)、rotate(旋转)、alpha(渐变),并且还支持这4种动画的任意组合动画,补间动画跟帧动画一样,都是作用于View之上的,View通过startAnimation(Animation anim)或setAnimation(Animation anim)来给自己添加补间动画,那Animation为何许类也?
我们可以看到,Animation拥有多个直接子类,我们可以通过这些子类直接在代码中创建相应类型的补间动画对象并启动动画,不仅如此,我们还能通过系统提供的AnimationUtils类的loadAnimation(Context context, int id) 方法直接从xml资源文件中加载动画对象,具体的请看下面代码
1.通过xml定义位移动画
开始编写代码之前,让我们先来看看要实现的效果吧,这样我们心里也能有个大致的方向
我们可以在res目录下新建anim文件夹,并在其内创建anim_tween_translate.xml文件,具体如下
//res右键=>new=>Android Resource File=>File name:anim_tween_translate(xml文件名字),Resource type:Animator,Directory name:anim(xml所在文件夹名字)=>ok
这是res目录的结构图,因为我是写好demo之后才写的博客,所以xml文件都有了
编写anim_tween_translate.xml位移动画文件,你会发现translate之内有好多属性,这些属性有些是translate独有的,有些是4种类型动画所共有的属性(因为这些属性是定义在Animation父类之内的),下面只是介绍了一部分常见的,因为公共属性都是一样的,所以在别的动画xml里也就不着重介绍了
<!--translate独有属性-->
<!--fromXDelta,toXDelta 动画的起始X坐标和动画结束后的X坐标-->
<!--fromYDelta,toYDelta 动画的起始Y坐标和动画结束后的Y坐标-->
<!--补间动画共有属性-->
<!--duration 动画所持续的时间-->
<!--startOffset 动画需要延迟多长时间再开始执行动画-->
<!--fillAfter 动画执行完成后是否需要停留在动画结束的状态-->
<!--repeatCount 动画执行次数=repeatCount+1,repeatCount="infinite"则无限循环-->
<!--android:repeatMode 重复类型reverse:表示倒序回访,restart:表示重新放一遍,这个属性必须与repeatCount联合使用,因为它的前提是重复,即重复播放时的播放类型-->
<!--interpolator 动画的插值器,影响动画执行速度:
accelerate_interpolator 动画加速器,动画执行速度 开始最慢=>逐渐加速
decelerate_interpolator 动画减速器,动画执行速度 开始最快=>逐渐减速
accelerate_decelerate_interpolator 动画加速减速器,动画执行速度 开始最慢=>中间最快=>结束最慢
-->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="48"
android:fromYDelta="0"
android:toYDelta="300"
android:duration="3000"
android:startOffset ="0"
android:fillAfter="false"
android:repeatCount="0"
android:interpolator="@android:anim/accelerate_interpolator"
/>
我们有了xml文件,可以直接通过系统提供的AnimationUtils类从xml中加载动画对象,随后启动位移动画
/** 通过xml加载位移动画*/
private void startTranslateAnimByXml() {
anim = AnimationUtils.loadAnimation(this, R.anim.anim_tween_translate);
imgTween.startAnimation(anim);
}
直接在代码中定义位移动画
如果你没有创建translate的xml文件,那不妨来使用一下TranslateAnimation类来实现一下上面的位移动画效果吧
/** 通过java code加载位移动画*/
private void startTranslateAnimByCode() {
//参数依次为:动画起始X坐标,动画执行结束时X坐标,起始Y坐标,动画执行结束时Y坐标
anim = new TranslateAnimation(0, 48, 0, 300);
anim.setDuration(3000);//设置动画执行时间
anim.setStartOffset(0);//动画需要延迟多长时间再开始执行动画
anim.setFillAfter(false);//动画执行完成后是否需要停留在动画结束的状态
anim.setRepeatCount(0);//设置执行次数,infinite为循环执行
imgTween.startAnimation(anim);
}
2.通过xml定义缩放动画
在开始代码之前,来让我们看下缩放动画的效果吧
在anim目录下,编写anim_tween_scale.xml,其中属性信息如下
<!--fromXScale,toXScale 动画起始时,动画执行结束时,X缩放倍数(大于1为放大,小于1为缩小)-->
<!--fromYScale,toYScale 动画起始时,动画执行结束时,Y缩放倍数-->
<!--duration 动画执行时间-->
<!--pivotX,pivotY 主要有三种取值
pivotX取值50 则意思是view左上角x坐标,加上50px,则为缩放轴点x
pivotX取值50% 则意思是view左上角x坐标,加上当前View的宽度的50%,则为缩放轴线x
pivotX取值50%p 则意思是View左上角坐标,加上当前View的父View宽度的50%,则为缩放轴点x
-->
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0.2"
android:toXScale="1"
android:fromYScale="0.2"
android:toYScale="1"
android:duration="3000"
android:pivotX="0%"
android:pivotY="0%"
/>
我们有了xml文件,可以直接通过系统提供的AnimationUtils类从xml中加载动画对象,随后启动缩放动画
/** 通过xml加载缩放动画*/
private void startScaleAnimByXml() {
anim = AnimationUtils.loadAnimation(this, R.anim.anim_tween_scale);
imgTween.startAnimation(anim);
}
直接在代码中定义缩放动画
如果你没有创建scale的xml文件,那不妨来使用一下ScaleAnimation类来实现一下上面的缩放动画效果吧
/** 通过java code加载缩放动画*/
private void startScaleAnimByCode() {
//参数依次为:动画起始时x缩放倍数,动画结束时x的缩放倍数,起始时y的缩放倍数,结束时y的缩放倍数,横向x的缩放轴点,竖向y的缩放轴点
anim = new ScaleAnimation(0.2f, 1f, 0.2f, 1f, 0f, 0f);
anim.setDuration(3000);//动画执行时间
imgTween.startAnimation(anim);
}
3.通过xml定义旋转动画
在开始代码之前,也让我们来看下动画效果吧
在anim目录下,编写anim_tween_rotate.xml,其属性信息如下
<!--fromDegrees 动画开始时的角度-->
<!--toDegrees 动画结束时的角度-->
<!--pivotX,pivotY 动画以哪个点为轴点进行旋转-->
<!--duration 动画执行时间-->
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="10"
android:toDegrees="90"
android:pivotX="0"
android:pivotY="100%"
android:duration="3000"
/>
我们有了xml文件,可以直接通过系统提供的AnimationUtils类从xml中加载动画对象,随后启动旋转动画
/** 通过xml加载旋转动画*/
private void startRotateAnimByXml() {
anim = AnimationUtils.loadAnimation(this, R.anim.anim_tween_rotate);
imgTween.startAnimation(anim);
}
在代码中直接定义旋转动画
如果你没有创建rotate的xml文件,那不妨来使用一下RotateAnimation类来实现一下上面的旋转动画效果吧
/** 通过java code加载旋转动画*/
private void startRotateAnimByCode() {
//参数依次为:动画起始时的角度,动画结束时的角度,旋转轴点的x坐标的模式,控件进行旋转动画时的x轴点,旋转轴点的y坐标的模式,控件进行旋转动画时的x轴点
anim = new RotateAnimation(10,90,Animation.RELATIVE_TO_SELF,0f,Animation.RELATIVE_TO_SELF, 1f);
anim.setDuration(3000);//动画执行时间
imgTween.startAnimation(anim);
}
4.通过xml定义渐变动画
在开始编写代码之前,让我们来看下渐变动画效果吧
在anim目录下,编写anim_tween_alpha.xml,其属性定义如下
<!--fromAlpha 动画开始时的透明度-->
<!--toAlpha 动画结束时的透明度-->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0"
android:toAlpha="1"
android:duration="3000"
/>
我们有了xml文件,可以直接通过系统提供的AnimationUtils类从xml中加载动画对象,随后启动渐变动画
/** 通过xml加载透明度动画*/
private void startAlphaAnimByXml() {
anim = AnimationUtils.loadAnimation(this, R.anim.anim_tween_alpha);
imgTween.startAnimation(anim);
}
在代码中直接定义渐变动画
如果你没有创建alpha的xml文件,那不妨来使用一下RotateAnimation类来实现一下上面的渐变动画效果吧
/** 通过java code加载透明度动画*/
private void startAlphaAnimByCode() {
//参数依次为:动画开始时的透明度,动画结束时的透明度
anim = new AlphaAnimation(0, 1);
anim.setDuration(3000);
imgTween.startAnimation(anim);
}
5.通过xml定义一个组合动画
在开始编写代码之前,让我们先来看一下组合动画效果
在anim目录下,编写anim_tween_more_set.xml,有自己的属性,子标签也可以设置自己的属性,当两个标签都有相同属性时,动画执行时以相应的子标签属性为准,在标签内,每个子标签通过android:startOffset =”0”延迟时间来控制出场顺序
<!--shareinterpolator 组合动画中的动画是否和集合共享同一个差值器-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
>
<translate
android:fromXDelta="0"
android:toXDelta="48"
android:fromYDelta="0"
android:toYDelta="300"
android:duration="3000"
android:startOffset ="0"
android:fillAfter="false"
android:repeatCount="0"
android:interpolator="@android:anim/accelerate_interpolator"
/>
<scale
android:fromXScale="0.2"
android:toXScale="1"
android:fromYScale="0.2"
android:toYScale="1"
android:duration="3000"
android:pivotX="0%"
android:pivotY="0%"
android:startOffset ="3000"
/>
<rotate
android:fromDegrees="10"
android:toDegrees="90"
android:pivotX="0"
android:pivotY="100%"
android:duration="3000"
android:startOffset ="6000"
/>
<alpha
android:fromAlpha="1"
android:toAlpha="0"
android:duration="3000"
android:startOffset ="9000"
/>
</set>
有了xml文件,同样的我们也可以直接通过系统提供的AnimationUtils类从xml中加载动画对象,随后启动set组合动画
/** 通过xml加载一个组合动画*/
private void startMoreAnimByXml() {
anim = AnimationUtils.loadAnimation(this, R.anim.anim_tween_more_set);
imgTween.startAnimation(anim);
}
在代码中直接定义一个组合动画
如果你没有创建set的组合xml文件,那不妨来使用一下AnimationSet类来实现一下上面的组合动画效果吧
/** 通过java code加载一个组合动画*/
private void startMoreAnimByCode() {
animSet = new AnimationSet(true);
//创建位移动画
Animation animTranslate = new TranslateAnimation(0, 48, 0, 300);
animTranslate.setDuration(3000);//设置动画执行时间
animTranslate.setStartOffset(0);//动画需要延迟多长时间再开始执行动画
animTranslate.setFillAfter(false);//动画执行完成后是否需要停留在动画结束的状态
animTranslate.setRepeatCount(0);//设置执行次数,infinite为循环执行
//创建缩放动画
Animation animScale = new ScaleAnimation(0.2f, 1f, 0.2f, 1f, 0f, 0f);
animScale.setDuration(3000);//动画执行时间
animScale.setStartOffset(3000);//等第一个动画执行完,再执行
//创建旋转动画
Animation animRotate = new RotateAnimation(10,90,Animation.RELATIVE_TO_SELF,0f,Animation.RELATIVE_TO_SELF, 1f);
animRotate.setDuration(3000);//动画执行时间
animRotate.setStartOffset(6000);
//创建透明度动画
Animation animAlpha = new AlphaAnimation(1, 0);
animAlpha.setDuration(3000);
animAlpha.setStartOffset(9000);
//将每个动画添加到组合动画里
animSet.addAnimation(animTranslate);
animSet.addAnimation(animScale);
animSet.addAnimation(animRotate);
animSet.addAnimation(animAlpha);
//开始动画
imgTween.startAnimation(animSet);
}
四、总结
到这里我们就基本介绍完了帧动画和补间动画的基本使用方法,当然这只是动画知识框架里的一个皮毛,但是万丈高楼平地起,基础好了,再以此为基点向外发散,不断补充,不断深入发掘,总会有形成自己的一套体系的时候。