漫画Android:动画是如何实现的?

我来用通俗易懂的语言,结合生活中的例子,给你讲讲Android动画框架的实现原理。

想象一下,我们想让一个方块在屏幕上从左边滑到右边。

1. 动画的本质:快速翻页的连环画

动画的核心原理,其实就像我们小时候玩的"连环画"或者"翻书动画"。你快速翻动书页,每一页上的图画都只比前一页稍微变动一点点,最终就看到了一个会动的画面。 在Android屏幕上,这个"翻书"的速度非常快,通常每秒翻60次(也就是60帧),这样人眼就感觉不到一帧一帧的跳跃,而是看到了连续流畅的运动。

2. 早期的"假"动画:视图动画 (View Animation)

想象你有一个方块,你想让它动起来。早期的Android动画(叫"视图动画"或"补间动画")是怎么做的呢?

它就像在方块的玻璃投影上做手脚

  • 你有一个方块的"真身"在屏幕左边,它是静止不动的。
  • 动画系统生成一个投影
  • 然后,它以极快的速度(每秒60次)不断地改变这个投影的位置:第一帧投影在左边,第二帧稍微往右一点,第三帧再往右一点......
  • 屏幕上你看到的是这个会动的投影,感觉方块在动。

问题来了: 如果你点击这个"动起来"的方块,你会发现它没反应!因为你点的是投影,而方块的"真身"还在屏幕的左边老地方待着呢。这就是"幽灵点击"问题,因为它只改了绘制效果,没改真实属性。

3. 现代的"真"动画:属性动画 (Property Animation)

后来的Android系统(3.0版本之后),觉得这种"假"动画不行,它引入了"属性动画"。这次,我们不再改变投影,而是直接改变方块的"真实属性"。 这就像我们请了两个"动画师"来帮忙:

a. 动画值计算师 (ValueAnimator):

这个动画师非常聪明,它只做一件事:在规定时间内,根据你设定的规则,帮你计算出一个个"中间值"

  • 你告诉它:我要从"位置0"动到"位置100",用1秒钟。
  • 它会说:好的,0.01秒的时候我在"位置10",0.02秒的时候我在"位置20",以此类推......
  • 它就像一个专业的"数学家",只负责计算这些中间值,它不关心这些值是用来移动方块的,还是用来改变颜色的,它只管算。
  • 动画速度控制器 (Interpolator / 插值器): 这个计算师还有一个助手,叫"动画速度控制器"。你告诉它,你是想让动画匀速(像跑步机一样),还是先慢后快(像汽车启动),还是先快后慢(像电梯停下)。这个控制器会根据你的要求,调整计算师算值的"节奏"。
    • 匀速(线性插值器):1秒跑100米,0.5秒就跑50米。
    • 加速(加速插值器):0.5秒可能只跑了20米,但后0.5秒跑了80米。

b. 对象属性操纵师 (ObjectAnimator):

这个动画师是动画值计算师的进阶版,它更厉害,它知道怎么把"动画值计算师"算出来的值,直接应用到对象的"真实属性"上

  • 你告诉它:我要让"对象"的"X轴位置"动起来。
  • 它会监听"动画值计算师"算出来的每一个中间值。
  • 每当"动画值计算师"算出一个新值,比如"位置90",对象属性操纵师就会立刻找到该对象,并真正地把它在X轴上移动到"位置90"
  • 这次,该对象的"真身"是真的动了!所以你点击它,它就在新位置响应了。

4. 动画的"节拍器":Choreographer (节奏大师)

所有这些动画师都需要一个统一的"节拍器",确保大家步调一致,而且动画在最恰当的时机显示在屏幕上。这个"节拍器"就是Choreographer,我们可以叫它"节奏大师"。

  • 你的手机屏幕有刷新频率,通常每秒60次。可以想象成每隔1/60秒,屏幕就会问:"有没有新的画面要画?"
  • Choreographer就像一个经验丰富的舞台总监,它会监听屏幕的"刷新信号"(这个信号非常精确,叫Vsync,垂直同步信号)。
  • 每当屏幕发出"我要刷新了"的信号时,Choreographer就会立刻通知所有正在运行的动画:"快!现在是刷新时间,赶紧计算你们的下一帧!"
  • 这样,所有的动画计算和UI更新都会和屏幕的刷新频率完美同步,避免了画面撕裂(画面上下半部分不同步的丑陋现象),让动画看起来极其流畅。

5. 动画的"画图工人":GPU (图形处理器) 和 硬件加速

最后,计算出来的动画效果,最终要画到屏幕上。这个"画图工人"就是你手机里的GPU(图形处理器)

  • 在早期,画图都是CPU在干,又慢又累。
  • 现在Android默认开启了硬件加速。这意味着,当你的方块改变位置、大小、透明度时,Android会把这些"画图指令"打包,直接发给GPU去处理。
  • GPU专门干这个,速度飞快。而且,它还会把方块的初始样子先"刻"下来(叫"显示列表"),下次只是移动方块,它就不需要重新画一遍,直接把已经"刻"好的方块移动到新位置就行了,效率极高。

总结:

  1. 动画本质:快速播放一系列微小变化的画面。
  2. 视图动画:只改"投影",不改"真身",有"幽灵点击"的问题。
  3. 属性动画
    • ValueAnimator(动画值计算师) :负责计算动画的中间值,不关心具体是啥,只管算。有Interpolator(动画速度控制器)调节速度。
    • ObjectAnimator(对象属性操纵师) :把ValueAnimator算出的值,真正应用到对象的实际属性上(比如方块的X轴位置)。
  4. Choreographer(节奏大师):统一的"节拍器",监听屏幕刷新信号,确保动画与屏幕刷新同步,流畅不撕裂。
  5. 硬件加速 + GPU(画图工人):将画图任务交给专业的GPU,并利用显示列表等技术,大大提高动画渲染效率,让动画丝滑流畅。

属性动画的启动过程

1. ValueAnimator.start()ObjectAnimator.start():启动动画。

2. 注册 Choreographer.FrameCallback:动画内部(ValueAnimator)将一个FrameCallback注册到Choreographer

Choreographer 是动画和UI渲染的心跳(节奏大师),它将所有UI更新与Vsync信号同步。

模拟源码分析:

当你调用ValueAnimator.start()时,它最终会注册一个FrameCallbackChoreographer

java 复制代码
// 模拟 ValueAnimator 内部注册 FrameCallback 的逻辑
public void start() {
    // ...
    // 获取 Choreographer 实例
    Choreographer choreographer = Choreographer.getInstance();
    // 注册一个动画帧回调
    choreographer.postFrameCallback(mAnimationCallback); // mAnimationCallback 是 ValueAnimator 内部的 FrameCallback 实例
    // ...
}

// 模拟 ValueAnimator 内部 mAnimationCallback 的实现
private final FrameCallback mAnimationCallback = new FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        // 在这里进行动画的计算和更新
        doAnimationFrame(frameTimeNanos);
        // 如果动画还在进行,继续注册下一帧回调
        if (mRunning) {
            Choreographer.getInstance().postFrameCallback(this);
        }
    }
};

Choreographer 内部机制简述:

Choreographer 内部有一个Handler (mHandler) 和一个Looper。 当Vsync信号到来时(由DisplayManagerService或其他底层服务通知),它会向mHandler发送一个消息。当这个消息被处理时,mHandler会遍历所有已注册的FrameCallback并调用它们的doFrame()方法。

3. Vsync 信号到来:显示器发出Vsync信号,Choreographer收到通知。

4. Choreographer 回调 doFrame()Choreographer执行所有已注册的FrameCallbackdoFrame()方法。

5. ValueAnimator.doAnimationFrame() 执行:

  • 计算动画播放时间。
  • 通过mInterpolator.getInterpolation()计算插值进度。
  • 通过mEvaluator.evaluate()计算当前动画值。

ValueAnimator 是属性动画的基石(动画值计算师),它负责在给定的时间和范围内计算出动画的当前值。

模拟源码分析 (doAnimationFrame 核心逻辑):

java 复制代码
// ValueAnimator.java (简化版)
void doAnimationFrame(long frameTimeNanos) {
    if (mStartTime < 0) { // 第一次回调
        mStartTime = frameTimeNanos;
    }

    // 计算动画已经播放的时间
    mCurrentPlayTime = (frameTimeNanos - mStartTime) / 1_000_000L; // 转换为毫秒

    // 计算动画的进度 (0.0 to 1.0)
    float fraction = 0f;
    if (mDuration > 0) {
        fraction = (float)mCurrentPlayTime / mDuration;
        fraction = Math.min(fraction, 1.0f); // 确保不超过1.0
    }

    // 应用时间插值器 (Interpolator)
    fraction = mInterpolator.getInterpolation(fraction);

    // 应用类型估值器 (Evaluator) 计算最终动画值
    mAnimatedValue = mEvaluator.evaluate(fraction, mStartValue, mEndValue);

    // 通知所有注册的更新监听器
    notifyUpdateListeners();

    // 检查动画是否结束
    if (mCurrentPlayTime >= mDuration) {
        endAnimation(); // 结束动画,停止注册下一帧回调
    }
}

private void notifyUpdateListeners() {
    for (AnimatorUpdateListener listener : mUpdateListeners) {
        listener.onAnimationUpdate(this); // 回调监听器,ValueAnimator 自身作为参数
    }
}

6. ObjectAnimator 应用属性:

如果使用ObjectAnimator,它会使用反射(或更高效的Property机制)调用目标对象的setter方法,将计算出的动画值应用到指定属性上。 例如,view.setAlpha(newAlphaValue);

ObjectAnimator - 将动画值应用到属性(对象属性操纵师); ObjectAnimator 继承自 ValueAnimator,它在 ValueAnimator 计算出值的基础上,通过反射(或直接调用setter)将值应用到目标对象的特定属性上。

模拟源码分析 (ObjectAnimator 如何利用 ValueAnimator):

ObjectAnimator 重写了 ValueAnimatoranimateValue() 方法(或者更准确地说,ValueAnimator 在调用notifyUpdateListeners()后,ObjectAnimator 会在内部处理属性设置)。

java 复制代码
// ObjectAnimator.java (简化版)
// ObjectAnimator 内部通常会有一个内部的 AnimatorUpdateListener
// 这个监听器会在 ValueAnimator 计算出新值后被回调

// 构造函数或 setupProperty 期间
public ObjectAnimator(Object target, String propertyName) {
    mTarget = target;
    mPropertyName = propertyName;
    // ... 内部会尝试通过反射找到 setter 方法或 Property 对象
}

// 假设 ObjectAnimator 内部有一个类似这样的监听器
// 在 ValueAnimator 的 notifyUpdateListeners() 之后被调用
private void applyAnimatedValue() {
    // 这里的 getAnimatedValue() 是 ValueAnimator 的方法,获取当前计算出的动画值
    Object value = getAnimatedValue();
    // 使用反射或其他方式设置属性值
    // 以下是伪代码,实际实现会更复杂,会缓存 Method 对象等
    try {
        if (mSetterMethod != null) { // 如果找到了 setter 方法
            mSetterMethod.invoke(mTarget, value);
        } else if (mProperty != null) { // 如果是 Property 对象
            mProperty.set(mTarget, value);
        } else {
            // Log.e("ObjectAnimator", "No setter or Property found for " + mPropertyName);
        }
    } catch (Exception e) {
        // 处理反射异常
    }
}

7. View.invalidate():被改变属性的View(例如setAlpha内部)会调用invalidate()方法,标记自身为"脏"。

ObjectAnimator 调用了View.setX()View.setAlpha()等方法更新了View的属性后,这些setter方法内部通常会调用 invalidate()requestLayout()

invalidate() 方法并不会立即重绘View。它会标记View为"脏",并发送一个请求到UI线程的ViewRootImpl

8. ViewRootImpl 的渲染循环:在下一个Vsync周期,ViewRootImplFrameCallback被调用,它检测到"脏"View。

9. View 重新绘制:ViewRootImpl执行绘制操作,调用受影响View的draw()方法。

ViewRootImpl 的渲染循环:

  • ViewRootImpl是连接View层级和窗口管理器(Window Manager)的关键类。
  • 它内部也会注册一个Choreographer.FrameCallback
  • 当Vsync信号到来时,ViewRootImplFrameCallback会被调用。
  • 在这个回调中,ViewRootImpl会检查是否有"脏"的View需要重绘。
  • 如果有,它会执行draw()方法,遍历View树,调用需要重绘的View的draw()方法。
  • 最终,绘制指令会被发送到图形系统(Skia/OpenGL ES),并由GPU进行渲染。

10. 硬件加速渲染:绘制指令(可能通过更新DisplayList)被发送到GPU进行渲染,最终显示在屏幕上。

在硬件加速开启的情况下,View的draw()方法会将绘制指令记录到DisplayList中。后续的动画更新如果只是改变了View的变换(如位置、透明度),而不需要重新执行所有复杂的绘制指令,GPU可以直接根据更新后的变换矩阵重放DisplayList,大大提高了渲染效率。

11. 循环:如果动画未结束,ValueAnimator会再次注册FrameCallbackChoreographer,等待下一个Vsync,重复以上步骤。

相关推荐
帅得不敢出门4 分钟前
Android Studio同一个工程根据不同芯片平台加载不同的framework.jar及使用不同的代码
android·android studio·jar
xiangxiongfly91518 分钟前
Android LeakCanary源码分析
android·leakcanary
黄林晴18 分钟前
紧急预警!Android 17 定位权限大改,你的 App 要适配了
android
夏沫琅琊43 分钟前
Android API 发送短信技术文档
android·kotlin
周周不一样1 小时前
Android基础笔记1
android·笔记·gitee
取码网1 小时前
影视APP源码 SK影视 安卓+苹果双端APP 反编译详细视频教程+源码
android
musk12121 小时前
android webview 黑屏问题 , 页面加载时间有点长的情况下
android
夏沫琅琊1 小时前
Android 彩信导出技术文档
android·kotlin
sp42a1 小时前
安卓原生 MQTT 通讯 Java 实现
android·java·mqtt