动画综述
Google大大对动画的总述如下:
Animations can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.
没错,放上原文我只是装个逼,~
简单来说,动画就两个作用:
- 添加可视提示,通知我们这个APP中正在发生的事情。比如用户界面发生变化时,有新的内容加载或某些操作变为可用。
- 提供高逼格的外观(装逼利器)
动画的分类如下:
属性动画(Property Animation)
概述
- 视图动画的缺陷:
- 对象的局限性:仅限于View
- 只改变了View的视觉效果,而没有改变View的属性
- 动画效果单一
- 属性动画的特点:
- 作用对象:任意对象,甚至没对象也可以
- 作用方式:改变对象的属性
- 动画效果:按需自定义,不再局限于上述4种基本变换
-
继承关系
Java类名 | XML关键字 | 说明 |
---|---|---|
ValueAnimator |
<animator> 放置在res/animator/目录下 |
在一个特定的时间里执行一个动画 |
TimeAnimator | 无 | 时序监听回调工具 |
ObjectAnimator |
<objectAnimator> 放置在res/animator/目录下 |
一个对象的一个属性动画 |
AnimatorSet |
<set> 放置在res/animator/目录下 |
动画集合 |
工作原理
指定时间内,修改属性(对象中对应的字段)的值,以此实现该对象在属性上的动画效果。
为了更好的理解,我们举一个栗子:
图1中我们搞出了一个假象的对象,动画作用于这个对象(实际可以说是对象的x属性,也即对象的水平位置),动画持续40ms,移动距离40px。每10ms(默认刷新速率),对象水平移动10px。40ms后,动画结束,物体停止在x=40处。这是一个典型的设置了linearInterpolator(匀速插值器)的动画。
为了更好的了解属性动画的工作原理,下面我们来看一看属性动画的组件是怎么计算上面例子的动画的。
其逻辑可以总结如下:
- 为 ValueAnimator 设置动画的时长,以及对应属性的始 & 末值
- 设置属性在 始 & 末值 间的变化逻辑
- TimeInterpolator实现类:插值器-描述动画的变化速率
- TypeEvaluator实现类:估值器-描述 属性值 变化的具体数值
- 根据2中的逻辑更新当前值
- 获取3中更新的 值 ,修改 目标属性值
- 刷新视图。
- 重复4-5,直到 属性值 == 末值
下面给出动画工作的关键类
Java类 | 说明 |
---|---|
ValueAnimator | 动画执行类;核心 |
ObjectAnimator | 动画执行类 |
TimeInterpolator | 时间插值(插值器接口),控制动画变化率 |
TypeEvaluator | 类型估值(估值器接口),设置属性值计算方式,根据属性的 始 & 末值 和 插值 一起计算出当前时间的属性值 |
AnimatorSet | 动画集 |
AnimatorInflater | 加载属性动画的XML文件 |
一些额外的类
Java类 | 说明 |
---|---|
LayoutTransition | 布局动画,为布局的容器设置动画 |
ViewPropertyAnimator | 为View的动画操作提供一种更加便捷的用法 |
PropertyValuesHolder | 保存动画过程中所需要操作的属性和对应的值 |
Keyframe | 控制每个时间段执行的动画距离 |
AnimationListener AnimationUpdateListener AnimatorListenerAdapter |
动画事件的监听 |
具体使用
ValueAnimator
- 属性动画的最核心的类
- 原理:控制 值 的变化,之后 手动 赋值给对象的属性,从而实现动画
对于控制的 值 的不同,Android 提供给我们三种构造方法来实例ValueAnimator对象
- ValueAnimator.ofInt(int... values) -- 整型数值
- ValueAnimator.ofFloat(float... values) -- 浮点型数值
- ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型
下面我们一一介绍
ValueAnimator.ofInt()
- 作用:将初始值 以整型数值的形式 过渡到结束值
- 估值器:内置
IntEvaluator
估值器 - 具体使用:
操作 值 的方式分为 XML 方式/ Java 代码方式
方式1: Java 方式
推荐 Java 方式,因为某些时候我们需要动态获取属性的起始值,显然XML方式是不支持动态获取的。
//设置动画 始 & 末值
//ofInt()两个作用:
//1. 获取实例
//2. 在传入参数之间平滑过渡
//如下则0平滑过渡到3
ValueAnimator animator = ValueAnimator.ofInt(0,3);
//如下传入多个参数,效果则为0->5,5->3,3->10
//ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);
//设置动画的基础属性
animator.setDuration(5000);//播放时长
animator.setStartDelay(300);//延迟播放
animator.setRepeatCount(0);//重放次数
animator.setRepeatMode(ValueAnimator.RESTART);
//重放模式
//ValueAnimator.START:正序
//ValueAnimator.REVERSE:倒序
//设置更新监听
//值 改变一次,该方法就执行一次
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取改变后的值
int currentValue = (int) animation.getAnimatedValue();
//输出改变后的值
Log.d("1111", "onAnimationUpdate: " + currentValue);
//改变后的值发赋值给对象的属性值
view.setproperty(currentValue);
//刷新视图
view.requestLayout();
}
});
//启动动画
animator.start();
以上就是一个标准的Java方式的模板
方式2: XML 方式
- 在路径
res/animator/
路径下常见 XML 文件,如set_animator.xml
- 在上述文件中设置动画参数
// ValueAnimator采用<animator> 标签
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse"/>
/>
- Java代码启动动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 载入XML动画
animator.setTarget(view);
// 设置动画对象
animator.start();
// 启动动画
ValueAnimator.ofFloat()
- 作用:将初始值 以浮点型数值的形式 过渡到结束值
- 估值器:内置
FloatEvaluator
估值器 - 具体使用:
和ValueAnimator.ofInt()及其类似,以下只说明不同之处,省略部分参考ofInt()
方式1:Java方式
ValueAnimator anim = ValueAnimator.ofFloat(0, 3);
//只是改了实例方法,除此之外完全一样
方式2:XML方式
只在设置动画 XML 文件中的属性时略有不同
// ValueAnimator 采用 <animator> 标签
// ObjectAnimator 采用 <objectAnimator> 标签
<animatorxmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueTo="200"
android:valueType="floatType"
android:propertyName="y"
android:repeatCount="1"
android:repeatMode="reverse"/>
ValueAnimator.ofObject()
- 作用:将初始值 以对象的形式 过渡到结束值
- 估值器:Android 不提供,需要自定义估值器
- 具体使用:
ValueAnimator.ofObject() 属于 ValueAnimator 的高级用法,我们前面提到的对任意对象进行动画操作,就是通过此方法实现的。
以下先放上示例模板:
// 创建初始动画的对象 & 结束动画的对象
Point point1 = new Point ();
Point point2 = new Point ();
// 创建动画对象 & 设置参数
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), point1 , point2 );
// 参数说明
// 1. 自定义的估值器对象(TypeEvaluator 类型参数) - 下面会详细介绍
// 2. 初始动画的对象
// 3. 结束动画的对象
anim.setDuration(length);
anim.start();
上面示例中,我们看到有两个Point对象point1 , point2 。假设我们有一个自定义View,这个View中有一个Point对象用于管理坐标,然后我们在onDraw()方法中根据这个Point对象的坐标值进行绘制,也就是说,我们可以对Point对象进行动画操作,不停的根据Point的坐标刷新View重绘制,以此就可以实现 View 的动画了。
到这里都很好理解,不过我们还注意到传入了一个 new myObjectEvaluator()
(TypeEvaluator
实现类) 参数,这是干什么的呢?不要急,下面我们就详细解答。
TypeEvaluator 估值器
其实我们已经不止一次的提到估值器的概念,如 ValueAnimator.ofFloat()
方法中的 IntEvaluator
, ValueAnimator.ofFloat()
方法中的 FloatEvaluator
,这两种都是Android预置供我们使用的,二者通过计算告知动画系统如何从初始值过渡到结束值。
但是二者虽然好用,但也有其局限性:只能针对 Int / Float 类型数值操作。因此某些我们需要对任意对象进行动画操作的时候,二者显然不能满足我们的需求了,这时候我们需要自定义一个TypeEvaluator来告知系统如何进行过渡。
那么如何自定义呢?别急,我们可以先看一看系统提供的 IntEvaluator
是如何实现的:
/**
* This evaluator can be used to perform type interpolation between int values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* fraction representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
* where x0 is startValue, x1 is endValue, and t is fraction.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type int or Integer
* @param endValue The end value; should be of type int or Integer
* @return A linear interpolation between the start and end values, given the fraction parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
源码很简单,注释也相当详细,方便观看,我再次解释一下:
IntEvaluator
实现了 TypeEvaluator
接口,然后重写了 evaluate()
方法。该方法的三个参数意义如下:
- fraction:表示动画完成度,据此计算当前动画的值
- startValue:动画初始值
- endValue:动画结束值
那么 evalute()
方法的返回值就不难理解了,简单的数学公式,返回当前动画的值。
是不是很简单?因此我们自定义 TypeEvaluator
时只需要实现 TypeEvaluator
接口,然后重写 evaluate()
方法,在此方法中处理好逻辑即可。
下面我们就动手写一个自定义 TypeEvaluator
自定义TypeEvaluator
我们还是以上面提过的Point对象管理View坐标的为例:
- 定义
Point
类
public class Point {
//记录坐标位置
private float x;
private float y;
//通过构造方法设置坐标,因此不需要额外的set方法
public Point(float x, float y) {
this.x = x;
this.y = y;
}
//get方法,获取当前坐标值
public float getX() {
return x;
}
public float getY() {
return y;
}
}
- 定义
PointEvaluator
//实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {
//重写evaluate()方法
@Override
public Object evaluate(float fraction, Object startValue, Object 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());
//返回以上述x,y组装的新的Point对象
Point point = new Point(x,y);
return point;
}
}
Point
对象间的平滑过渡
// 创建初始动画的对象 & 结束动画的对象
Point point1 = new Point(0, 0);
Point point2 = new Point(500, 500);
// 创建动画对象 & 设置参数
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1 , point2 );
anim.setDuration(3000);
anim.start();
以上就是自定义TypeEvaluator的全部用法。
下面我们就可以尝试用上述知识练习如何对对象进行动画操作,从而实现自定义View的动画效果
自定义View的动画效果
- 新建MyAnimView继承View
public class MyAnimView extends View {
//常量
public static final float RADIUS = 50f;
//当前Point,记录当前动画的值(x,y坐标)
private Point curPoint;
//画笔
private Paint mPaint;
//Java代码实例化View时调用
public MyAnimView(Context context) {
super(context);
}
//XML文件实例时调用
public MyAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
//第一次绘制时
if (curPoint == null){
//初始化坐标为(50f, 50f)
curPoint = new Point(RADIUS,RADIUS);
//画圆
drawCircle(canvas);
//开始动画
startAnimation();
}else {//非第一次绘制
drawCircle(canvas);
}
}
//在当前坐标处绘制一个半径为50f的圆
private void drawCircle(Canvas canvas) {
float x = curPoint.getX();
float y = curPoint.getY();
canvas.drawCircle(x,y,RADIUS,mPaint);
}
//开始动画
private void startAnimation() {
//设置 起始值&结束值
Point startPoint = new Point(RADIUS,RADIUS);//起始为左上角(50f,50f)
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//终点为右下角(屏幕宽度-50f,屏幕高度-50f)
final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
//设置插值器
anim.setInterpolator(new BounceInterpolator());
//设置监听
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//每当Point的值有改变的时候,都会调用onAnimationUpdate()方法
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//更新curPoint,即更新当前坐标
curPoint = (Point) animation.getAnimatedValue();
// 刷新,重现调用onDraw()方法
// 由于curPoint的值改变,那么绘制的位置也会改变,也就实现了一个从左上到右下的平移动画
invalidate();
}
});
anim.setDuration(5000)