漫画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,重复以上步骤。

相关推荐
VirusVIP29 分钟前
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
android·ide·android studio
androidwork3 小时前
嵌套滚动交互处理总结
android·java·kotlin
fatiaozhang95274 小时前
中兴B860AV1.1强力降级固件包
android·adb·电视盒子·av1·机顶盒rom·魔百盒刷机
橙子199110165 小时前
Kotlin 中的 Object
android·开发语言·kotlin
AD钙奶-lalala9 小时前
android:foregroundServiceType详解
android
大胃粥12 小时前
Android V app 冷启动(13) 焦点窗口更新
android
fatiaozhang952716 小时前
中兴B860AV1.1_晨星MSO9280芯片_4G和8G闪存_TTL-BIN包刷机固件包
android·linux·adb·电视盒子·av1·魔百盒刷机
fatiaozhang952717 小时前
中兴B860AV1.1_MSO9280_降级后开ADB-免刷机破解教程(非刷机)
android·adb·电视盒子·av1·魔百盒刷机·移动魔百盒·魔百盒固件
二流小码农17 小时前
鸿蒙开发:绘制服务卡片
android·ios·harmonyos
微信公众号:AI创造财富17 小时前
adb 查看android 设备的硬盘及存储空间
android·adb