一、动画的作用
在Android开发的过程中,View的变化是很常见的,如果View变化的过程没有动画来过渡而是瞬间完成,会让用户感觉很不友好。
二、插值器和估值器
1、插值器
对于补间动画:插值器(TimeInterpolator)的作用是根据时间流逝的百分比计算出动画进度的百分比。有了动画进度的百分比,就可以很容易的计算出动画开始的关键帧与将要显示的帧之间的差异(通过Transformation类的对象表示),下面展示TranslateAnimation类中如何根据动画进度的百分比计算出动画开始的关键帧与将要显示的帧之间的差异:
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}
applyTransformation方法的第一个参数就是通过时间插值器(TimeInterpolator)获取的动画进度的百分比。然后根据帧之间的差异绘制出将要显示的帧,以此类推从而形成动画的效果。
对于属性动画:插值器(TimeInterpolator)的作用是根据时间流逝的百分比计算出动画进度的百分比(即属性值改变的百分比)。
插值器汇总表如下:
2、类型估值器
类型估值器(TypeEvaluator)是针对于属性动画框架的,对于View动画框架是不需要类型估值器的。类型估值器的作用是根据属性值改变的百分比计算出改变后的属性值。由于属性动画实际上作用的是对象的属性,而属性的类型是不同的,因此Android内置了一些常用的类型估值器来操作不同类型的属性,下图是所有估值器
三、帧动画
帧动画和动画片的原理一样。帧动画要求开发者把动画过程中的每张静态图片收集起来,然后由Android控制依次显示这些静态图片,利用人类的视觉暂留原理,给用户造成动画的错觉。
1、帧动画的定义方式
帧动画在xml中定义,在drawable目录下新建xml,具体格式如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot=["true" | "false"]>
<item android:drawable="@drawable/drawable_resource_name"
android:duration="integer"/>
</animation-list>
android:oneshot控制是否循环播放。true不循环播放,false循环播放。
item表示动画中的一帧。android:duration表示一帧持续的时间。
具体例子如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/a_0"
android:duration="100"/>
<item android:drawable="@drawable/a_1"
android:duration="100"/>
<item android:drawable="@drawable/a_2"
android:duration="100"/>
</animation-list>
2、启动动画
AnimationDrawable默认是不播放的,必须调用start()才开始播放,调用stop()可以停止播放。
如何获取AnimationDrawable?
把上面定义xml设置到ImageView中,再调用ImageView.getBackground()获取。具体代码如下:
ImageView ivFrameAnimation = (ImageView) findViewById(R.id.iv_frame_animation);
ivFrameAnimation.setImageResource(R.drawable.frame_animation);
AnimationDrawable animationDrawable = (AnimationDrawable) ivFrameAnimation.getDrawable();
animationDrawable.start();
四、补间动画
补间动画指开发者只需指定动画的开始和动画结束关键帧以及动画时间,动画变化的中间帧由系统计算并补齐。
1、补间动画的分类
AlphaAnimation(透明度动画)、RotateAnimation(旋转动画)、ScaleAnimation(缩放动画)、TranslateAnimation(平移动画)四种类型的补间动画
2、具体使用
1)、平移动画
使用xml定义
a、在 res/anim的文件夹里创建动画效果.xml文件
b、定义动画
<?xml version="1.0" encoding="utf-8"?>
// 采用<translate /> 标签表示平移动画
<translate xmlns:android="http://schemas.android.com/apk/res/android"
// 以下参数是4种动画效果的公共属性,即都有的属性
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true。(感觉没用)
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
// 以下参数是平移动画特有的属性
android:fromXDelta="0" // 视图在水平方向x 移动的起始值
android:toXDelta="500" // 视图在水平方向x 移动的结束值
android:fromYDelta="0" // 视图在竖直方向y 移动的起始值
android:toYDelta="500" // 视图在竖直方向y 移动的结束值
/>
c、在代码中将该动画设置到View中
Animation animation = AnimationUtils.loadAnimation(TweenedTestActivity.this, R.anim.translate_animation);
animationExecutor.startAnimation(animation);
使用java代码定义
// 步骤1:创建 需要设置动画的 视图View
Button animationExecutor= (Button) findViewById(R.id.btn_animation_executor);
// 步骤2:创建平移动画的对象:平移动画对应的Animation子类为TranslateAnimation
// 参数分别是:
// 1. fromXDelta :视图在水平方向x 移动的起始值
// 2. toXDelta :视图在水平方向x 移动的结束值
// 3. fromYDelta :视图在竖直方向y 移动的起始值
// 4. toYDelta:视图在竖直方向y 移动的结束值
Animation translateAnimation = new TranslateAnimation(0,500,0,500);
translateAnimation.setDuration(3000);
// 步骤3:播放动画
animationExecutor.startAnimation(translateAnimation);
2)、缩放动画
使用xml定义
a、在 res/anim的文件夹里创建动画效果.xml文件
b、定义动画
<?xml version="1.0" encoding="utf-8"?>
// 采用<scale/> 标签表示是缩放动画
<scale xmlns:android="http://schemas.android.com/apk/res/android"
// 以下参数是4种动画效果的公共属性,即都有的属性
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
// 以下参数是缩放动画特有的属性
android:fromXScale="0.0"
// 动画在水平方向X的起始缩放倍数
// 0.0表示收缩到没有;1.0表示正常无伸缩
// 值小于1.0表示收缩;值大于1.0表示放大
android:toXScale="2" //动画在水平方向X的结束缩放倍数
android:fromYScale="0.0" //动画开始前在竖直方向Y的起始缩放倍数
android:toYScale="2" //动画在竖直方向Y的结束缩放倍数
android:pivotX="50%" // 缩放轴点的x坐标
android:pivotY="50%" // 缩放轴点的y坐标
// 轴点 = 视图缩放的中心点
// pivotX pivotY,可取值为数字,百分比,或者百分比p
// 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
// 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
// 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
// 两个50%表示动画从自身中间开始
/>
pivotX pivotY,可取值为数字,百分比,或者百分比p:
1、设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
2、设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
3、设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
c、在代码中将该动画设置到View中
Animation animation = AnimationUtils.loadAnimation(TweenedTestActivity.this, R.anim.scale_animation);
animationExecutor.startAnimation(animation);
使用java代码定义
// 步骤1:创建 需要设置动画的 视图View
Button animationExecutor = (Button) findViewById(R.id.btn_animation_executor);
// 步骤2:创建缩放动画的对象 & 设置动画效果:缩放动画对应的Animation子类为RotateAnimation
// 参数说明:
// 1. fromX :动画在水平方向X的结束缩放倍数
// 2. toX :动画在水平方向X的结束缩放倍数
// 3. fromY :动画开始前在竖直方向Y的起始缩放倍数
// 4. toY:动画在竖直方向Y的结束缩放倍数
// 5. pivotXType:缩放轴点的x坐标的模式
// 6. pivotXValue:缩放轴点x坐标的相对值
// 7. pivotYType:缩放轴点的y坐标的模式
// 8. pivotYValue:缩放轴点y坐标的相对值
// pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
// pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
// pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
Animation scaleAnimation = new ScaleAnimation(0,2,0,2,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
// 固定属性的设置都是在其属性前加“set”,如setDuration()
scaleAnimation.setDuration(3000);
// 步骤3:播放动画
animationExecutor.startAnimation(scaleAnimation);
3)、旋转动画
使用xml定义
a、在 res/anim的文件夹里创建动画效果.xml文件
b、定义动画
<?xml version="1.0" encoding="utf-8"?>
// 采用<rotate/> 标签表示是旋转动画
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
// 以下参数是4种动画效果的公共属性,即都有的属性
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
// 以下参数是旋转动画特有的属性
android:duration="1000"
android:fromDegrees="0" // 动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
android:toDegrees="270" // 动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
android:pivotX="50%" // 旋转轴点的x坐标
android:pivotY="0" // 旋转轴点的y坐标
// 轴点 = 视图缩放的中心点
// pivotX pivotY,可取值为数字,百分比,或者百分比p
// 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
// 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
// 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
// 两个50%表示动画从自身中间开始,具体如下图
/>
android:fromDegrees: 动画开始时视图的旋转角度(正数 = 顺时针旋转,负数 = 逆时针旋转)
android:toDegrees:动画结束时视图的旋转角度(正数 = 顺时针旋转,负数 = 逆时针旋转)
c、在代码中将该动画设置到View中
Animation animation = AnimationUtils.loadAnimation(TweenedTestActivity.this, R.anim.rotate_animation);
animationExecutor.startAnimation(animation);
使用java代码定义
// 步骤1:创建 需要设置动画的 视图View
Button animationExecutor = (Button) findViewById(R.id.btn_animation_executor);
// 步骤2:创建旋转动画的对象 & 设置动画效果:旋转动画对应的Animation子类为RotateAnimation
// 参数说明:
// 1. fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
// 2. toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
// 3. pivotXType:旋转轴点的x坐标的模式
// 4. pivotXValue:旋转轴点x坐标的相对值
// 5. pivotYType:旋转轴点的y坐标的模式
// 6. pivotYValue:旋转轴点y坐标的相对值
// pivotXType = Animation.ABSOLUTE:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
// pivotXType = Animation.RELATIVE_TO_SELF:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
// pivotXType = Animation.RELATIVE_TO_PARENT:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
Animation rotateAnimation = new RotateAnimation(0, 270, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
// 固定属性的设置都是在其属性前加“set”,如setDuration()
rotateAnimation.setDuration(3000);
// 步骤3:播放动画
animationExecutor.startAnimation(rotateAnimation);
4)、透明度动画
使用xml定义
a、在 res/anim的文件夹里创建动画效果.xml文件
b、定义动画
<?xml version="1.0" encoding="utf-8"?>
// 采用<alpha/> 标签表示是透明度动画
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
// 以下参数是4种动画效果的公共属性,即都有的属性
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
// 以下参数是透明度动画特有的属性
android:fromAlpha="1.0" // 动画开始时视图的透明度(取值范围: -1 ~ 1)
android:toAlpha="0.0"// 动画结束时视图的透明度(取值范围: -1 ~ 1)
/>
c、在代码中将该动画设置到View中
Animation animation = AnimationUtils.loadAnimation(TweenedTestActivity.this, R.anim.alpha_animation);
animationExecutor.startAnimation(animation);
使用java代码定义
// 步骤1:创建 需要设置动画的 视图View
Button animationExecutor = (Button) findViewById(R.id.btn_animation_executor);
// 步骤2:创建透明度动画的对象 & 设置动画效果:透明度动画对应的Animation子类为AlphaAnimation
// 参数说明:
// 1. fromAlpha:动画开始时视图的透明度(取值范围: -1 ~ 1)
// 2. toAlpha:动画结束时视图的透明度(取值范围: -1 ~ 1)
Animation alphaAnimation = new AlphaAnimation(1,0);
// 固定属性的设置都是在其属性前加“set”,如setDuration()
alphaAnimation.setDuration(3000);
// 步骤3:播放动画
animationExecutor.startAnimation(alphaAnimation);
五、属性动画
与属性动画相比View动画存在一个缺陷,View动画改变的只是View的显示,而没有改变View的响应区域,并且View动画只能对View做四种类型的补间动画,因此Google在Android3.0及其后续版本中添加了属性动画框架。同样属性动画框架还提供了动画集合类(AnimatorSet),通过动画集合类(AnimatorSet)可以将多个属性动画以组合的形式显示出来。
1、原理
只要某个类具有属性(即该类含有某个字段的set和get方法),那么属性动画框架就可以对该类的对象进行动画操作(其实就是通过反射技术来获取和执行属性的get,set方法),因此属性动画框架可以实现View动画框架的所有动画效果并且还能实现View动画框架无法实现的动画效果。属性动画框架工作原理可以总结为三步:
1)、 在创建属性动画时如果没有设置属性的初始值,Android系统就会通过该属性的get方法获取初始值,所以在没有设置属性的初始值时,必须提供该属性的get方法,否者程序会Crash。
2)、在动画播放的过程中,属性动画框架会利用时间流逝的百分比获取属性值改变的百分比(即通过时间插值器),接着利用获取的属性值改变的百分比获取改变后的属性值(即通过类型估值器)。
3)、 通过该属性的set方法将改变后的属性值设置到对象中。还要注意一点,虽然通过set方法改变了对象的属性值,但是还要将这种改变用动画的形式表现出来,否者就不会有动画效果,所以属性动画框架本身只是不断的改变对象的属性值并没有实现动画效果。因为让控件重绘才能实现动画。
2、如何使用属性动画
1)、ViewPropertyAnimator
ViewPropertyAnimator中方法和View中方法对照表:
通过View.animate()就可以获取ViewPropertyAnimator。使用代码如下:
Button animationExecutor = findViewById(R.id.btn_animation_executor);
// 设置水平位移
animationExecutor.animate().translationX(700).setDuration(3000).start();
// 设置水平方向缩放
animationExecutor.animate().scaleX(2).setDuration(3000).start();
// 设置平面旋转
animationExecutor.animate().rotation(90).setDuration(3000).start();
// 设置透明度变化
animationExecutor.animate().alpha(0.2f).setDuration(3000).start();
2)、ObjectAnimator
使用方式:
a、如果是自定义控件,需要添加 setter / getter 方法;
b、用ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象;
c、用 start() 方法执行动画。
代码如下:
Button animationExecutor = findViewById(R.id.btn_animation_executor);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(animationExecutor, "translationX", 600);
objectAnimator.setDuration(3000);
objectAnimator.start();
3)、ValueAnimator
ValueAnimator类就是一个数值生成器,没有上面关于属性动画框架工作原理的第1步和第3步,ObjectAnimator作为ValueAnimator的子类,实现了这两步(第1步和第3步)。只要给ValueAnimator提供一个初始值、结束值和周期时间,ValueAnimator就会按照属性动画框架工作原理的第2步中的步骤生成具有一定规则的数字。要实现动画效果还需要我们添加代码。代码如下:
Button animationExecutor = findViewById(R.id.btn_animation_executor);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(600);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// ValueAnimator只返回动画的具体值,不会改变控件的属性,需要我们自己设置新的值到控件中。这样才实现动画效果
animationExecutor.setTranslationX((Float) animation.getAnimatedValue());
}
});
valueAnimator.start();
4)三种实现的区别
ViewPropertyAnimator只能实现部分属性的动画但操作简单。ObjectAnimator可以实现所有属性的动画但操作复杂。ValueAnimator是ObjectAnimator的父类,只产生变化过程中属性的值,但是没有把这个值设置到属性中,这步需要开发者完成。
3、属性动画不起作用怎么办?
ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start(); 为Button设置该属性动画,但是Button的宽度并不会改变。因为setWidth()方法并不是改变Button的宽度。
1)、 给你的对象加上get和set方法
如果你有权限的话,加上get和set就搞定了,但是很多时候我们没权限去这么做,比如上面所提到的问题,你无法给Button加上一个合乎要求的setWidth方法,因为这是Android SDK内部实现的。
2)、 用一个类来包装原始对象,间接为其提供get和set方法
private void performAnimate() {
ViewWrapper wrapper = new ViewWrapper(mButton);
// 注意这里是最包装类使用属性动画
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate();
}
}
// 包装类,内部实现控件宽度的改变
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
注意上面是对包装类使用属性动画。
3)、 采用ValueAnimator,监听动画过程,自己实现属性的改变
private void performAnimate(final View target, final int start, final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
//持有一个IntEvaluator对象,方便下面估值的时候使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animator) {
//获得当前动画的进度值,整型,1-100之间
int currentValue = (Integer)animator.getAnimatedValue();
Log.d(TAG, "current value: " + currentValue);
//计算当前进度占整个动画过程的比例,浮点型,0-1之间
float fraction = currentValue / 100f;
//直接调用整型估值器通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate(mButton, mButton.getWidth(), 500);
}
}
建议使用第二种方法,比较简单