Android 属性动画原理解析

目录

属性动画简介

什么是属性动画

属性动画的基本模型

android 属性动画使用示例

ValueAnimator

ObjectAnimator

ViewPropertyAnimator

属性动画的执行流程

动画循环

代码实现

帧刷新机制

Android Display系统的工作方式

早期帧刷新机制

优化:Project Butter

代码实现

根据动画已播放时长计算属性值

线性动画示例

非线性动画示例

时间插值器

类型估值器

代码实现

设置属性值

代码实现

界面绘制

Android 绘制模型

总结

Android属性动画实现原理

兼容性问题

硬件加速

内存泄漏

参考文档

Choreographer相关

属性动画

硬件加速


属性动画简介

什么是属性动画

顾名思义,属性动画就是通过改变对象的属性做动画

想象一个自由落体运动,在下落过程中,从最高点落到最低点,物体的位置不断变化,物体其他属性没有发生变化。我们要模拟自由落体运动,就可以通过改变物体的位置来实现这个动画效果。在这个例子中,位置就是物体的一个属性,动画效果被抽象成位置属性的连续变化,属性控制了画面的显示效果。通过这种方式制作的动画就称为属性动画

属性动画的基本模型

一个属性动画通常由以下基本元素构成:

  • 动画对象
    • 动画操作的对象,通过改变其属性值使得该对象的显示效果发生改变
  • 动画属性
    • 随着的动画播放,值连续改变的属性
  • 动画时长
    • 动画持续的时间
  • 动画刷新频率
    • 每秒画面刷新的次数
  • 动画时间到属性值的映射关系
    • 指定如何根据动画的当前进度来计算属性的值
    • 可以分为线性映射关系和非线性映射关系,从而将动画分为线性动画和非线性动画
  • 动画绘制
    • 根据新的属性值绘制新画面的过程

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 ,让程序可以绘制下一帧
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值