目录
属性动画简介
什么是属性动画
顾名思义,属性动画就是通过改变对象的属性做动画
想象一个自由落体运动,在下落过程中,从最高点落到最低点,物体的位置不断变化,物体其他属性没有发生变化。我们要模拟自由落体运动,就可以通过改变物体的位置来实现这个动画效果。在这个例子中,位置就是物体的一个属性,动画效果被抽象成位置属性的连续变化,属性控制了画面的显示效果。通过这种方式制作的动画就称为属性动画
属性动画的基本模型
一个属性动画通常由以下基本元素构成:
- 动画对象
- 动画操作的对象,通过改变其属性值使得该对象的显示效果发生改变
- 动画属性
- 随着的动画播放,值连续改变的属性
- 动画时长
- 动画持续的时间
- 动画刷新频率
- 每秒画面刷新的次数
- 动画时间到属性值的映射关系
- 指定如何根据动画的当前进度来计算属性的值
- 可以分为线性映射关系和非线性映射关系,从而将动画分为线性动画和非线性动画
- 动画绘制
- 根据新的属性值绘制新画面的过程
android 属性动画使用示例
Android属性动画是在Android 3.0 之后出现的,属性动画系统是一个功能强大的框架,可用于为几乎任何对象添加动画效果。属性动画会在指定时长内更改属性(对象中的字段)的值
Android 属性动画支持定义动画的以下特性:
- 时长
- 时间插值函数
- 重复计数和行为
- 指定是否在某个时长结束后重复播放动画以及重复播放动画多少次;还可以指定是否要反向播放动画。如果将其设置为反向播放,则会先播放动画,然后反向播放动画,直到达到重复次数
- AnimatorSet
- 将动画分成多个逻辑集,它们可以一起播放、按顺序播放或者在指定的延迟时间后播放
要为对象属性添加动画效果,需要指定要添加动画效果的对象属性,例如对象在屏幕上的位置、动画效果持续多长时间以及要在哪些值之间添加动画效果
Android提供了一组属性动画API,可以方便的创建属性动画。属性动画的使用方法按照封装程度从低到高(自由度从高到低)依次为ValueAnimator、ObjectAnimator、ViewPropertyAnimator三种,其中ValueAnimator是核心,ObjectAnimator继承于ValueAnimator,ViewPropertyAnimator 内部采用ValueAnimator 实现动画
ValueAnimator
ValueAnimator是一个数值发生器,它并不会直接改变属性的值,而是用来产生随动画进度变化的数值,间接控制动画的实现过程。我们需要做的就是监听这些值的改变,改变View的属性,进而产生动画效果
用法
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setTarget(mTextView); // 这行代码没用,ValueAnimator的该方法是空实现
animator.setDuration(4000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTextView.setAlpha(animation.getAnimatedFraction());
}
});
animator.start();
ObjectAnimator
ObjectAnimator提供了更简便的属性设置方式。在ValueAnimator的基础之上,其内部方法通过反射方式调用对象某个属性的set方法。因此这个对象的属性需要具有set/get方法
用法
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView, "alpha", 0, 1);
objectAnimator.setDuration(4000);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();
如果需要对控件的多个属性执行动画,有两种方式可以实现:
1、使用PropertyValuesHolder实现
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("alpha", 0, 1);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX", 0, 1);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY", 0, 1);
ObjectAnimator.ofPropertyValuesHolder(mTextView, pvh1, pvh2, pvh3)
.setDuration(4000)
.start();
2、使用AnimatiorSet实现。相对于第一种方法,AnimatorSet可以对播放顺序进行更精准的控制。该类通过playTogether、playSequentially、play().with()、play().before()、play().after()等方法支持协调多个动画的播放顺序,可以实现连续动画、多属性复杂动画等。
ObjectAnimator o1 = ObjectAnimator.ofFloat(mTextView, "alpha", 0, 1);
ObjectAnimator o2 = ObjectAnimator.ofFloat(mTextView, "scaleX", 0, 1);
ObjectAnimator o3 = ObjectAnimator.ofFloat(mTextView, "scaleY", 0, 1);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(4000);
animatorSet.playTogether(o1, o2, o3);
animatorSet.start();
ViewPropertyAnimator
为了更方便地对View使用属性动画,Google 很贴心地为View增加了animate方法直接创建属性动画,开发者可以更方便、更优雅地实Vview的属性动画,此时返回的对象是ViewPropertyAnimater的实例。
ViewPropertyAnimator 内部使用单个 ValueAnimator 对象为 View 的多个属性并行添加动画效果。它的行为方式与 ObjectAnimator 非常相似,它会修改视图属性的实际值,但在同时为多个属性添加动画效果时,更为高效。使用 ViewPropertyAnimator 的代码也更简洁,也更易读
mTextView.animate()
.alpha(1)
.scaleX(2f)
.scaleY(2f)
.setDuration(4000)
.start();
属性动画的执行流程
我们了解了属性动画的基本组成与用法,接下来看看属性动画是如何工作的。简化的属性动画的执行过程可以借助以下伪代码描述:
long startTime = System.currentTimeMillis();
int propStartVal = 0, propEndVal = 100;
int propVal = propStartVal;
long dt = 0, duration = 1000;
while(dt < duration){ // --1、动画循环
Thread.sleep(20); // --2、等待下一帧刷新
dt = System.currentTimeMillis() - starttime;// --3、根据动画进度计算属性值
float fraction = dt / duration;
float currVal = fraction * (propEndVal - propStartVal);
propVal = currVal; // --4、设置属性值
draw(); // --5、执行绘制
}
属性动画的执行流程可以分为动画循环、等待帧刷新、计算属性值、更新属性值和绘制画面五个部分,下面将对每一部分进行详细说明
动画循环
在动画周期中,动画一直进行,画面需要不断地刷新。在Android系统中,一切事件都由消息机制驱动,UI线程如果在5秒内不能响应用户交互,就会发生崩溃。所以动画循环不能一直运行阻塞住UI线程,需要遵循消息机制实现重绘循环。我们需要在每一次帧刷新消息到来时,更新属性并重绘View。因此动画处理程序要在动画开始时,注册帧刷新的回调;在动画结束时,解除注册的帧刷新回调
代码实现
注册帧刷新回调
// ValueAnimator.class
private void start(boolean playBackwards) {
/***部分代码省略***/
addAnimationCallback(0);
/***部分代码省略***/
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
public AnimationHandler getAnimationHandler() {
return AnimationHandler.getInstance();
}
AnimationHandler是一个负责管理应用程序注册的动画帧回调接口的类, 应用程序通过该类注册和解注册帧刷新回调,该类维护一个回调队列,当动画帧刷新事件到来时,统一调用队列中的回调接口来通知应用程序,该类实例是一个ThreadLocal类型的对象,一个线程仅有一份
调用其addAnimationFrameCallback 方法会将该回调接口添加到其内部维护的回调队列 mAnimationCallbacks中
public class AnimationHandler {
public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
public static AnimationHandler getInstance() {
if (sAnimatorHandler.get() == null) {
sAnimatorHandler.set(new AnimationHandler());
}
return sAnimatorHandler.get();
}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// mFrameCallback:FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
}
AnimationFrameCallbackProvider是一个跟动画协调对象Choreographer交互的一个接口
MyFrameCallbackProvider 内部持有一个 Choreographer 对象,该对象也是线程局部变量,即线程唯一的。MyFrameCallbackProvider的postFrameCallback方法内部调用了Choreographer 对象 的postFrameCallback方法添加FrameCallback类型的接口回调,该回调接口只有一个doFrame 方法,即在下一帧刷新时会回调该方法进行处理
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
}
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
解注册帧刷新回调
// ValueAnimator.class
private void endAnimation() {
if (mAnimationEndRequested) {
return;
}
removeAnimationCallback();
// ...
}
private void removeAnimationCallback() {
// ...
getAnimationHandler().removeCallback(this);
}
帧刷新机制
当下一帧刷新时,动画刷新属性值,然后重新绘制页面。那属性动画值是在什么时候刷新的呢?我们先了解一下Android 帧刷新的机制
Android Display系统的工作方式
手机屏幕是由许多微小的像素点组成的。通过为每个像素点设置不同的颜色,屏幕可以呈现丰富多彩的图像
屏幕展示的颜色数据
- 在GPU中有一块缓冲区叫做 Frame Buffer ,这个帧缓冲区可以认为是存储像素颜色值的二维数组
- 数组中的每一个值对应着手机屏幕的像素点需要显示的颜色
- 应用程序在绘制后将帧缓冲区的数据进行了修改,只要将修改后的数据刷新到屏幕上就可以显示绘制的图像了
- 至于刷新工作,Android系统会定期将 Frame Buffer刷新到显示屏上。目前主流的刷新频率为60次/秒,折算出来就是16ms刷新一次
GPU的Frame Buffer中的数据
- GPU 除了Frame Buffer用以交给手机屏幕进行绘制外,还有一个缓冲区 Back Buffer,用以交给应用程序往里面填充数据
- 系统会定期交换 Back Buffer 和 Frame Buffer ,也就是将Back Buffer中的数据交给屏幕进行显示,同时让原先的Frame Buffer 变成 Back Buffer ,让程序可以绘制下一帧