Android中的动画主要分为两大类,视图动画和属性动画。我相信这在我们参加面试的时候面试官必问的问题。但是关键的是当面试官问这一知识点的时候我们通常都是过不了关的。那么这里我们就着重讲一下视图动画这个分支。什么叫视图动画呢,顾名思义它主要针对的就是View视图。
视图动画又可分为两个分支:补间动画、逐帧动画
一、补间动画
根据不同的动画效果,补间动画又分为4种动画:
平移动画(Translate)缩放动画(scale)旋转动画(rotate)透明度动画(alpha)
1、在XML中的表现和使用
在XML中定义的动画动作文件应该存放在res/anim文件夹下,访问时采用R.anim.XXX.xml的方式,位置如图:
XML配置文件中分别对应如下标签
(1)scale标签——调节尺寸,进行缩放
scale标签是缩放动画,可以实现动态调控件尺寸的效果,有下面几个属性:
android:fromXScale 起始的X方向上相对自身的缩放比例,浮点值,比如1.0代表自身无变化,0.5代表起始时缩小一倍,2.0代表放大一倍;
android:toXScale 结尾的X方向上相对自身的缩放比例,浮点值;
android:fromYScale 起始的Y方向上相对自身的缩放比例,浮点值,
android:toYScale 结尾的Y方向上相对自身的缩放比例,浮点值;
android:pivotX缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,当为数值时,表示在当前View的左上角,即原点处加上50px,做为起始缩放点;如果是50%,表示在当前控件的左上角加上自己宽度的50%做为起始点;如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为起始点x轴坐标。(具体意义,后面会举例演示)
android:pivotY缩放起点Y轴坐标,取值及意义跟android:pivotX一样。
下面看一个实例,当scale里的属性这样设置时,效果会怎样呢:
<?xml version="1.0" encoding="utf-8"?><scale xmlns:android="/apk/res/android"android:fromXScale="0.0"android:toXScale="1.4"android:fromYScale="0.0"android:toYScale="1.4"android:pivotX="50"android:pivotY="50"android:duration="700" />
pivotX取值数值时(50)
这个控件,宽度和高度都是从0放大到1.4倍,起始点坐标在控件左上角(坐标原点),向x轴正方向和y轴正方向都加上50像素;
根据pivotX,pivotY的意义,控件的左上角即为控件的坐标原点,这里的起始点是在控件的原点的基础上向X轴和Y轴各加上50px,做为起始点,如下图中图二所示
图一 图二
pivotX取值百分数时(50%)
下面再看看当pivotX、pivotY取百分数的时候,起始点又在哪里?
上面我们讲了,pivotX的值,当取50%时,表示在原点坐标的基础上加上的自己宽度的50%,看看效果:
<?xml version="1.0" encoding="utf-8"?><scale xmlns:android="/apk/res/android"android:fromXScale="0.0"android:fromYScale="0.0"android:toYScale="1.4"android:pivotX="50%"android:pivotY="50%"android:duration="700" />
缩放位置大小仍然从0-1.4,只改变pivotX和pivotY;起始点位置如下图中图二所示:
图一图二
pivotX取值50%p时
前面说过,当取值在百分数后面加上一个字母p,就表示,取值的基数是父控件,即在原点的基础上增加的值是父标签的百分值。
<?xml version="1.0" encoding="utf-8"?><scale xmlns:android="/apk/res/android"android:fromXScale="0.0"android:toXScale="1.4"android:fromYScale="0.0"android:toYScale="1.4"android:pivotX="50%p"android:pivotY="50%p"android:duration="700" />
效果图,及起始点坐标图如下所示:
图一 图二
(2)alpha标签——调节透明度
android:fromAlpha 动画开始的透明度,从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
android:toAlpha 动画结束时的透明度,也是从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
使用示例:
<?xml version="1.0" encoding="utf-8"?><alpha xmlns:android="/apk/res/android" android:fromAlpha="1.0" android:toAlpha="0.1" android:duration="3000" android:fillBefore="true"></alpha>
(3)rotate标签——旋转
android:fromDegrees 开始旋转的角度位置,正值代表顺时针方向度数,负值代码逆时针方向度数
android:toDegrees 结束时旋转到的角度位置,正值代表顺时针方向度数,负值代码逆时针方向度数
android:pivotX 缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,具体意义已在scale标签中讲述,这里就不再重讲
android:pivotY 缩放起点Y轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p
<?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="/apk/res/android" android:fromDegrees="0" android:toDegrees="-650" android:pivotX="50%" android:pivotY="50%" android:duration="3000" android:fillAfter="true"> </rotate>
围绕自身从0度逆时针旋转650度 围绕自身从0度顺时针旋转650度
android:fromDegrees="0" android:toDegrees="-650" android:fromDegrees="0" android:toDegrees="650"
(4)translate标签 —— 平移
android:fromXDelta 起始点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,具体意义已在scale标签中讲述,这里就不再重讲
android:fromYDelta 起始点Y轴从标,可以是数值、百分数、百分数p 三种样式;
android:toXDelta 结束点X轴坐标
android:toYDelta 结束点Y轴坐标
<?xml version="1.0" encoding="utf-8"?><translate xmlns:android="/apk/res/android" android:fromXDelta="0"android:toXDelta="-80" android:fromYDelta="0" android:toYDelta="-80" android:duration="2000" android:fillBefore="true"></translate>
(5)Animation的其他属性
Animation类是所有动画(scale、alpha、translate、rotate)的基类,这里以scale标签为例,讲解一下,Animation类所具有的属性及意义。
android:duration 动画持续时间,以毫秒为单位
android:fillAfter 如果设置为true,控件动画结束时,将保持动画最后时的状态
android:fillBefore 如果设置为true,控件动画结束时,还原到开始动画前的状态
android:fillEnabled 与android:fillBefore 效果相同,都是在动画结束时,将控件还原到初始化状态
android:repeatCount 重复次数
android:repeatMode 重复类型,有reverse和restart两个值,reverse表示倒序回放,restart表示重新放一遍,必须与repeatCount一起使用才能看到效果。因为这里的意义是重复的类型,即回放时的动作。
android:interpolator 设定插值器,其实就是指定的动作效果,比如弹跳效果等,不在这小节中讲解,后面会单独列出一单讲解。
对于android:duration,就不再讲解了,就是动画的持续时长,以毫秒为单位,下面看看android:fillAfter和android:fillBefore
android:fillAfter:保持动画结束的状态
<?xml version="1.0" encoding="utf-8"?><scale xmlns:android="/apk/res/android" android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50%" android:pivotY="50%" android:duration="700" android:fillAfter="true" />
android:fillBefore 还原初始化状态
<?xml version="1.0" encoding="utf-8"?><scale xmlns:android="/apk/res/android" android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50%" android:pivotY="50%" android:duration="700" android:fillBefore="true" />
android:fillBefore="true"android:fillEnable="true"
通过案例我们发现设定fillEnable="true"的效果和fillBefore="true"的效果相同,这两个的标签的效果完全相同。
android:repeatMode="restart /reverse" 设定回放类型
<?xml version="1.0" encoding="utf-8"?><scale xmlns:android="/apk/res/android" android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50%" android:pivotY="50%" android:duration="700"android:fillBefore="true" android:repeatCount="1" android:repeatMode="restart"/>
androidRepeatMode设为restartandroidRepeatMode设为reverse
(6)set标签——定义动作合集
前面我们讲解了各个标签动画的意义及用法,但他们都是独立对控件起作用,假设我现在想上面的textView控件做一个动画——从小到大,旋转出场,而且透明度也要从0变成1,即下面的这个效果,该怎么办?
这就需要对指定的控件定义动作合集,Set标签就可以将几个不同的动作定义成一个组;
set标签自已是没有属性的,他的属性都是从Animation继承而来,但当它们用于Set标签时,就会对Set标签下的所有子控件都产生作用。
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="/apk/res/android" android:duration="3000" android:fillAfter="true"><alphaandroid:fromAlpha="0.0" android:toAlpha="1.0"/> <scale android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50%" android:pivotY="50%"/> <rotate android:fromDegrees="0" android:toDegrees="720" android:pivotX="50%" android:pivotY="50%"/></set>public class MainActivity extends Activity { Button scaleBtn;Animation scaleAnimation;TextView tv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.setamintion);scaleBtn = (Button)findViewById(R.id.btn_animation);tv =(TextView)findViewById(R.id.tv);scaleBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubtv.startAnimation(scaleAnimation);}});} }
2、在Java代码中的表现和使用
在Java代码中每一种又对应着一个子类
(1)平移
private Animation getTranslateAnimation() {TranslateAnimation translateAnimation = new TranslateAnimation(0, getWidth() * 2,0, getHeight() * 2);translateAnimation.setDuration(2000);translateAnimation.setRepeatCount(2);translateAnimation.setFillAfter(true);translateAnimation.setFillBefore(false);translateAnimation.setRepeatMode(Animation.REVERSE);return translateAnimation;}
(2)缩放
private Animation getScaleAnimation() {ScaleAnimation scaleAnimation = new ScaleAnimation(1f, 2f,1f, 2f,getWidth() / 2, getHeight() / 2);scaleAnimation.setDuration(2000);scaleAnimation.setRepeatCount(2);scaleAnimation.setFillAfter(true);scaleAnimation.setFillBefore(false);scaleAnimation.setRepeatMode(Animation.REVERSE);return scaleAnimation;}
(3)旋转
private Animation getRotateAnimation() {RotateAnimation rotateAnimation = new RotateAnimation(0f, 360f,getWidth() / 2, getHeight() / 2);rotateAnimation.setDuration(2000);rotateAnimation.setRepeatCount(1);rotateAnimation.setFillAfter(true);rotateAnimation.setFillBefore(false);rotateAnimation.setRepeatMode(Animation.REVERSE);return rotateAnimation;}
(4)透明度
private Animation getAlphaAnimation() {AlphaAnimation alphaAnimation = new AlphaAnimation(1f, 0f);alphaAnimation.setDuration(2000);alphaAnimation.setRepeatCount(1);alphaAnimation.setFillAfter(true);alphaAnimation.setFillBefore(false);alphaAnimation.setRepeatMode(Animation.REVERSE);return alphaAnimation;}
(5)组合使用animationSet
private Animation getAnimationSet(boolean fromXML) {if (fromXML) {Animation animation = AnimationUtils.loadAnimation(this, R.anim.view_animation);return animation;} else {AnimationSet innerAnimationSet = new AnimationSet(true);innerAnimationSet.setInterpolator(new BounceInterpolator());innerAnimationSet.setStartOffset(1000);innerAnimationSet.addAnimation(getScaleAnimation());innerAnimationSet.addAnimation(getTranslateAnimation());AnimationSet animationSet = new AnimationSet(true);animationSet.setInterpolator(new LinearInterpolator());animationSet.addAnimation(getAlphaAnimation());animationSet.addAnimation(getRotateAnimation());animationSet.addAnimation(innerAnimationSet);return animationSet;}}
二、逐帧动画
将动画拆分为 帧 的形式,且定义每一帧 = 每一张图片逐帧动画的本质:按序播放一组预先定义好的图片具体使用
步骤1:将动画资源(即每张图片资源)放到 drawable文件夹里
技巧:
找到自己需要的gif动画用 gif分解软件(如 GifSplitter)将 gif 分解成一张张图片即可
步骤2:设置 & 启动 动画
设置 & 启动 逐帧动画有两种方式:在XML / Java代码。
方式1:XML实现
步骤1:在 res/anim的文件夹里创建动画效果.xml文件此处路径为res/anim/knight_attack.xml
步骤2:设置动画资源(图片资源)
knight_attack.xml
<?xml version="1.0" encoding="utf-8"?><animation-listxmlns:android="/apk/res/android"android:oneshot="true" // 设置是否只播放一次,默认为false>// item = 动画图片资源;duration = 设置一帧持续时间(ms)<item android:drawable="@drawable/a0" android:duration="100"/><item android:drawable="@drawable/a1" android:duration="100"/><item android:drawable="@drawable/a2" android:duration="100"/><item android:drawable="@drawable/a3" android:duration="100"/><item android:drawable="@drawable/a4" android:duration="100"/><item android:drawable="@drawable/a5" android:duration="100"/><item android:drawable="@drawable/a6" android:duration="100"/><item android:drawable="@drawable/a7" android:duration="100"/><item android:drawable="@drawable/a8" android:duration="100"/><item android:drawable="@drawable/a9" android:duration="100"/><item android:drawable="@drawable/a10" android:duration="100"/><item android:drawable="@drawable/a11" android:duration="100"/><item android:drawable="@drawable/a12" android:duration="100"/><item android:drawable="@drawable/a13" android:duration="100"/><item android:drawable="@drawable/a14" android:duration="100"/><item android:drawable="@drawable/a15" android:duration="100"/><item android:drawable="@drawable/a16" android:duration="100"/><item android:drawable="@drawable/a17" android:duration="100"/><item android:drawable="@drawable/a18" android:duration="100"/><item android:drawable="@drawable/a19" android:duration="100"/><item android:drawable="@drawable/a20" android:duration="100"/><item android:drawable="@drawable/a21" android:duration="100"/><item android:drawable="@drawable/a22" android:duration="100"/><item android:drawable="@drawable/a23" android:duration="100"/><item android:drawable="@drawable/a24" android:duration="100"/><item android:drawable="@drawable/a25" android:duration="100"/></animation-list>
步骤3:在Java代码中载入 & 启动动画
public class FrameActivity extends AppCompatActivity {private Button btn_startFrame,btn_stopFrame;private ImageView iv;private AnimationDrawable animationDrawable;iv = (ImageView) findViewById(R.id.iv);btn_startFrame = (Button) findViewById(R.id.btn_startFrame);btn_stopFrame = (Button) findViewById(R.id.btn_stopFrame);<-- 开始动画 -->btn_startFrame.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {iv.setImageResource(R.drawable.knight_attack);// 1. 设置动画animationDrawable = (AnimationDrawable) iv.getDrawable();// 2. 获取动画对象animationDrawable.start();// 3. 启动动画}});//停止动画btn_stopFrame.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {iv.setImageResource(R.drawable.knight_attack);// 1. 设置动画animationDrawable = (AnimationDrawable) iv.getDrawable();// 2. 获取动画对象animationDrawable.stop();// 3. 暂停动画}});}}
方式2:在Java代码中实现
<-- 直接从drawable文件夹获取动画资源(图片) -->animationDrawable = new AnimationDrawable();for (int i = 0; i <= 25; i++) {int id = getResources().getIdentifier("a" + i, "drawable", getPackageName());Drawable drawable = getResources().getDrawable(id);animationDrawable.addFrame(drawable, 100);}<-- 开始动画 -->btn_startFrame.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {animationDrawable.setOneShot(true);iv.setImageDrawable(animationDrawable);// 获取资源对象animationDrawable.stop();// 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次animationDrawable.start();// 启动动画}});<-- 停止动画 -->btn_stopFrame.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {animationDrawable.setOneShot(true);iv.setImageDrawable(animationDrawable);animationDrawable.stop();}});
但是逐帧动画使用需要注意每一帧图片的大小,避免OOM。