前言
在上一篇中,我们对Android三大动画的使用做了基本的介绍。那么本篇就来详细的说说属性动画。
首先我们得了解一下属性动画和传统动画之间的优缺点。
帧动画和补间动画的缺陷:
1.作用对象的局限性:
补间动画 只能够作用在视图View上,即只可以对一个Button、TextView、甚至是LinearLayout、或者其它继承自View的组件进行动画操作,但无法对非View的对象进行动画操作。所以当有时候我们只需要对某个视图的某个属性做动效的时候就无法用补间动画了。
2.补间动画改变的只是视觉上的效果,并没有真正改变其属性:
- 补间动画只是改变了view的视觉效果,并没有真正去改变view的属性。
- 如,将屏幕左上角的按钮 通过补间动画 移动到屏幕的右下角,点击当前按钮位置(屏幕右下角)是没有效果的,因为实际上按钮还是停留在屏幕左上角,补间动画只是将这个按钮绘制到屏幕右下角,改变了视觉效果而已。
3.动画效果单一,可扩展性差:
- 补间动画只能实现平移、旋转、缩放、透明度变化这些简单的动画。
- 比较复杂的动效,一旦超出上面四种,补间动画就无法实现。
纵观以上缺陷,属性动画便在API11(Android3.0)中出现了。属性动画可以弥补补间动画的可扩展性差的缺陷,它可以作用于任何java对象的任何属性,不再局限于四种基本变换了。
属性动画实现原理:
在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。
逻辑图:
上图中出现了ValueAnimator 和ObjectAnimator 两个类,这两个类是属性动画的核心类。
ObjectAnimator:
ObjectAnimator类是继承自ValueAnimator类的,ObjectAnimator实现动画的原理是通过不断控制值的变化,再不断 自动 赋给对象的属性,从而实现动画效果。关于ObjectAnimator类的使用可以看我上一篇博文(安卓三大动画介绍及使用)。
ValueAnimator:
定义:属性动画机制中 最核心的一个类。
实现动画的原理:通过不断控制值的变化,再不断 手动 赋给对象的属性,从而实现动画效果。
ValueAnimator类中有3个重要方法:
- ValueAnimator.ofInt(int values)
- ValueAnimator.ofFloat(float values)
- ValueAnimator.ofObject(int values)
ValueAnimator.ofInt(int values):
将初始值 以整型数值的形式 过渡到结束值,即估值器是整型估值器 - IntEvaluator。
具体使用如下:
Java代码设置:
实际开发中,建议使用Java代码实现属性动画:因为很多时候属性的起始值是无法提前确定的(无法使用XML设置),这就需要在Java代码里动态获取。
我们还是以上一篇中自定义imageview的宽度变化的属性动画为例:
private void customValueAnOfInt() {
// 步骤1:设置动画属性的初始值 & 结束值
ValueAnimator anim = ValueAnimator.ofInt(iv.getWidth(), 400, 150);
// ofInt()作用有两个
// 1. 创建动画实例
// 2. 将传入的多个Int参数进行平滑过渡:此处传入imageview的宽和400px以及150px,表示将值从imageview的宽平滑过渡到400,再平滑过渡到150
// 如果传入了多个Int参数 a,b,c...则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
// ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值
// 步骤2:设置动画的播放各种属性
anim.setDuration(500);
// 设置动画运行的时长
anim.setStartDelay(500);
// 设置动画延迟播放时间
anim.setRepeatCount(0);
// 设置动画重复播放次数 = 重放次数+1
// 动画播放次数 = infinite时,动画无限重复
anim.setRepeatMode(ValueAnimator.RESTART);
// 设置重复播放动画模式
// ValueAnimator.RESTART(默认):正序重放
// ValueAnimator.REVERSE:倒序回放
// 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器
// 设置 值的更新监听器
// 即:值每次改变、变化一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获得改变后的值
int currentValue = (Integer) animation.getAnimatedValue();
// 步骤4:将改变后的值赋给对象的属性值,下面会详细说明
ViewGroup.LayoutParams lp = iv.getLayoutParams();
lp.width = currentValue ;
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
iv.requestLayout();
}
});
anim.start();
// 启动动画
}
实现效果图:
XML中设置:
具备复用性,即将通用的动画写到XML里,可在各个界面中去复用它。
- 步骤1:在路径 res/animator的文件夹里创建相应的动画 .xml文件。
- 步骤2:设置动画参数:
// ValueAnimator采用<animator> 标签
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="100" // 初始值
android:valueTo="400" // 结束值
android:valueType="intType" // 变化值类型 :floatType & intType
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 // 插值器,即影响动画的播放速度
/>
- 步骤3:在java代码中设置动画:
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 载入XML动画
animator.setTarget(iv);
// 设置动画对象
animator.start();
// 启动动画
效果跟第一种方式是一样的。
ValueAnimator.ofFloat(float values)
ValueAnimator.ofFloat(float values)的使用方法与上面的ValueAnimator.ofInt(int values)的用法基本一致,所以此处就不再演示了。
ValueAnimator.ofObject()
作用:将初始值 以对象的形式 过渡到结束值。
具体使用:
// 创建初始动画时的对象 & 结束动画时的对象
myObject object1 = new myObject();
myObject object2 = new myObject();
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2);
// 创建动画对象 & 设置参数
// 参数说明
// 参数1:自定义的估值器对象(TypeEvaluator 类型参数) - 下面会进行介绍
// 参数2:初始动画的对象
// 参数3:结束动画的对象
anim.setDuration(5000);
anim.start();
这时候,众多的小伙伴们就开始疑惑了,自定义估值器又是个啥玩意呢?简直N脸懵逼,别慌,要稳住,接着往下看:
估值器(TypeEvaluator) 介绍:
- 估值器的作用:设置动画如何从初始值过渡到结束值的逻辑。
1.插值器(Interpolator)决定 值 的变化模式(匀速、加速…)。
2.估值器(TypeEvaluator)决定 值 的具体变化数值。 - ValueAnimator.ofInt() 和 ValueAnimator.ofFloat()都具备系统内置的估值器,即IntEvaluator & FloatEvaluator。因此对于这两种方法,系统已经默认的实现了动画从初始值过渡到结束值的逻辑。
附上FloatEvaluator的实现代码:
public class FloatEvaluator implements TypeEvaluator {
// FloatEvaluator实现了TypeEvaluator接口
// 重写evaluate()
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 参数说明
// fraction:表示动画完成度,即当前动画完成的百分比(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
// 初始值 过渡 到结束值 的算法是:
// 1. 用结束值减去初始值,算出它们之间的差值
// 2. 用上述差值乘以fraction系数
// 3. 再加上初始值,就得到当前动画的值
}
}
对于ValueAnimator.ofObject(),系统并没有为我们默认实现从初始对象过渡到结束对象的逻辑,所以我们需要自行定义估值器(TypeEvaluator)来告知系统如何实现从 初始对象 过渡到 结束对象的逻辑。
自定义逻辑如下:
// 实现TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator{
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 参数说明
// fraction:表示动画完成度(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
... // 写入对象动画过渡的逻辑
return value;
// 返回对象动画过渡的逻辑计算后的值
}
来个实例压压惊:
我们实现一个自定义动画让一个圆从屏幕左上角移动到屏幕右下角,先看看效果:
1.我们首先创建一个要操作的对象类(本例操作对象是圆的坐标类Point.java)
public class Point {
// 设置两个变量用于记录坐标的位置
private float x;
private float y;
// 构造方法用于设置坐标
public Point(float x, float y) {
this.x = x;
this.y = y;
}
// get方法用于获取坐标
public float getX() {
return x;
}
public float getY() {
return y;
}
}
2.实现TypeEvaluator接口
- 实现TypeEvaluator接口的目的是自定义如何 从初始点坐标 过渡 到结束点坐标。
- 本例实现的是一个从左上角到右下角的坐标过渡逻辑。
// 实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
// 根据fraction来计算当前动画的x和y的值
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
// 将计算后的坐标封装到一个新的Point对象中并返回
Point point = new Point(x, y);
return point;
}
}
3.自定义一个CircleView,并将属性动画作用到该CircleView上
/**
* Created by Dreamer__YY on 18/4/13.
*/
public class CircleView extends View {
// 设置需要用到的变量
public static final float RADIUS = 70f;// 圆的半径 = 70
private Point currentPoint;// 当前点坐标
private Paint mPaint;// 绘图画笔
// 构造方法(初始化画笔)
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
// 复写onDraw()从而实现绘制逻辑
// 绘制逻辑:先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
@Override
protected void onDraw(Canvas canvas) {
// 如果当前点坐标为空(即第一次)
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
// 创建一个点对象(坐标是(70,70))
// 在该点画一个圆:圆心 = (70,70),半径 = 70
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
// (重点关注)将属性动画作用到View中
// 步骤1:创建初始动画时的对象点 & 结束动画时的对象点
Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70)
Point endPoint = new Point(700, 1000);// 结束点为(700,1000)
// 步骤2:创建动画对象 & 设置初始值 和 结束值
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
// 参数说明
// 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口)
// 参数2:初始动画的对象点
// 参数3:结束动画的对象点
// 步骤3:设置动画参数
anim.setDuration(5000);
// 设置动画时长
// 步骤3:通过 值 的更新监听器,将改变的对象手动赋值给当前对象
// 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象
// 设置 值的更新监听器
// 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
// 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
// 从而更新当前坐标值(currentPoint)
// 步骤4:每次赋值后就重新绘制,从而实现动画效果
invalidate();
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
// 所以坐标值每改变一次,就会调用onDraw()一次
}
});
anim.start();
// 启动动画
} else {
// 如果坐标值不为0,则画圆
// 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
// 在该点画一个圆:圆心 = (30,30),半径 = 30
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
}
}
这样就可以实现CircleView从左上角移到右下角的动画啦。
注意:
- 从上面例子可以看出,其实ValueAnimator.ofObject()的本质还是操作 ** 值 **,只是是采用将 多个值 封装到一个对象里的方式 同时对多个值一起操作而已。
- 就像上面的例子,本质还是操作坐标中的x,y两个值,只是将其封装到Point对象里,方便同时操作x,y两个值而已。
结语:
痛苦的时间总是短暂的,感谢朋友们捧场坚持看完这篇文章,你们对属性动画和自定义属性动画的理解是不是又更深刻了呢?成功打通看友们的任督二脉。额…肚子有点饿了,准备吃午饭啦,人是铁饭是钢,一顿不吃也不慌。咱们下期再见!