Android中属性动画与 Transition

347 阅读13分钟

虽然在使用上有较大的差异,但是 Transition 框架的实现本质上依赖于属性动画。它的内部核心依赖了 Property Animation(属性动画)机制来完成对视图属性的动态过渡。简而言之,Transition 先去捕捉视图的属性变化,再使用属性动画的方式为这些属性变化创建并执行动画。

1. 核心概念

  1. 属性动画(Property Animation)

    • 引入于 Android 3.0(API 11),也称“View 动画框架的升级”或“动画框架”。
    • 通过不断更新对象(通常是 View)的某个或多个“可动画属性”(如 alpha, translationX, rotation, scaleX 等),从而改变该对象在屏幕上的外观或位置,形成动画效果。
    • 核心类为 ObjectAnimatorValueAnimatorAnimatorSet 等。
  2. Transition

    • 引入于 Android 4.4(API 19)的 Transition Framework,后来在 AndroidX/Support Library 中也提供支持(androidx.transition 包)。
    • 主要用于两个界面状态(Scene)或两个布局状态之间的过渡动画,例如“布局 A” 切换到 “布局 B” 时,系统会自动检测视图的变化(位置、大小、透明度等),并应用相应的过渡动画。
    • 核心类为 TransitionManagerTransitionSetAutoTransitionFadeSlideChangeBounds 等。

2. 主要原理差异

2.1 属性动画(Property Animation)

  • 通过 ValueAnimator/ObjectAnimator 修改对象属性值

    • 例如 translationX, alpha, rotation 等,这些都是真实地改变了 View 在屏幕上的绘制属性。
    • 并非只是以前“补间动画”(Tween Animation)的视觉假象,而是真正修改了 View 的坐标、大小、透明度等可绘制属性。
  • 动画过程可高度自定义

    • 可以在 ValueAnimatorupdateListener 里手动计算并应用任何你想更新的数值(不一定是 View 的属性)。
    • 适合灵活地控制复杂动画,如并行动画、序列动画、插值器修改、路径跟随等。
  • 重点在“对单个或多个属性的动画控制”

    • 属性动画关注的是“我要把哪个值从多少变到多少”,与布局、状态切换没有直接的抽象概念关系。
    • 一般做视图层的微动效、拖拽、旋转缩放等动画时,直接用属性动画就可以完成。

2.2 Transition(过渡动画)

  • 场景(Scene)与过渡(Transition)

    • Transition Framework 提供了“场景”与“过渡”的概念。
    • 一个场景(Scene)描述了界面布局的一种静态状态,当从场景 A 切换到场景 B 时,系统通过比较这两个场景中 View 层次、位置、大小的不同,自动生成或执行相应的“过渡动画(Transition)”。
  • 自动检测变化、自动应用动画

    • 在使用 TransitionManager.beginDelayedTransition(...)TransitionManager.go(scene, transition) 时,你只需要在代码中修改布局约束、View 的可见性等,系统就会自动捕捉到这些变化,并使用指定的 Transition(如 ChangeBounds, Fade, Slide 等)来在一定的时间内平滑过渡到新的布局状态。
  • 重点在“两个状态间的差异”

    • Transition 关注的是不同布局状态的差异,如位置、尺寸、可见性、层级关系等,自动完成从旧状态过渡到新状态。
    • 常用于界面布局的动态切换,比如在 ConstraintLayout 或者 XML 里把“布局 A”变更为“布局 B”时,想要一个平滑的过渡过程,就可以使用 Transition。

它们虽然都有“动画”二字,但本质针对的使用场景和思路是不同的,一个针对的事View本身,一个针对的事布局状态变化。

属性动画(Property Animation)

  • 着眼点:对一个或多个对象(通常是 View)的**“某些属性”**做动画更新,比如 translationX, rotation, alpha 等。

  • 典型场景

    • 一个按钮点击后要“跳跃”一下;
    • 一个列表项被拖拽时跟随手势移动;
    • 实现一个复杂的弹簧抖动效果、曲线路径运动等等。
  • 重点:你会手动编写“属性从A变到B”的逻辑,自己管理动画的时长、插值方式、更新步骤。如果涉及多个对象或多个属性,还需自己将它们打包成 AnimatorSet 做并行或串行执行。

总结一下,属性动画能提供极大的灵活度,想动画化“任何可改变的数值”,基本都能实现。比如,在 ValueAnimator 的监听里,甚至可以更新自己定义的某个“颜色值”或“进度值”,跟 View 并没有直接关系。

Transition(过渡动画)

  • 着眼点两个布局状态(Scene)之间的差异

  • 典型场景

    • 整个界面从“列表模式”切换到“网格模式”,想让每个子项位置和大小平滑过渡;
    • ConstraintLayout 里从一种约束切换到另一种约束,比如屏幕旋转导致布局变化、或在 MotionLayout 场景中有一组界面状态切换;
    • 打开/收起一个面板时,需要多个视图一起做平滑过渡。
  • 重点不关心每个视图要怎么动;只需告诉系统“我打算把这个布局里的某些东西改成另一种布局(或把某些 View 隐藏)”,系统检测出前后状态变化,然后自动给出一个基于 FadeSlideChangeBounds 等预设过渡效果,让界面在一段时间内“平滑”地完成这次变更。

也就是说,Transition 对布局变化非常“友好” ——改布局就行,系统自动捕捉位置、大小、可见性变化并执行过渡。不需要一个个写“这个 View 的 X 坐标从 100 变到 200”之类的逻辑。

3. 常见使用场景对比

使用场景属性动画(Property Animation)Transition(过渡动画)
单个元素的微动效常用 直接通过 ObjectAnimator 对某个视图属性做动画,比如按钮点击缩放、View滑动不常用 因为 Transition 更适合多个视图、多个布局的整体切换
多元素复杂动画常用 可以使用 AnimatorSet 将多个 ObjectAnimator 串行或并行执行也可用 如果是多 View 的共同过渡,需要同时变更布局中的多个元素位置或可见性,Transition 也能一次性搞定
布局之间的切换可行 但需手动写逻辑,监听动画进度去更新 View 的位置、大小等常用 只需要改变布局或约束,然后调用 TransitionManager.beginDelayedTransition(...),系统自动检测并执行动画
响应布局变化如果布局大小或位置变化,需要手动更新或重新执行动画可以自动检测 View 的位置、大小、可见性的改变,并做过渡动画
代码复杂度动画逻辑都需要手动编写、维护,灵活但繁琐定义或选择合适的 Transition(如 AutoTransition, ChangeBounds),然后只需写布局变更逻辑即可

4. 优点与不足

4.1 属性动画(Property Animation)

优点:

  1. 非常灵活,能动画任何可控的属性(不仅仅是 View,也可以是自定义对象的任意字段)。
  2. 结合插值器(Interpolator)和估值器(TypeEvaluator)可以做出各式各样复杂的效果。
  3. 可与手势交互、拖拽或绘图等需求深度结合。

不足:

  1. 一旦场景涉及多个视图或布局的大幅变动,需要繁琐地控制每个视图的动画。
  2. 无法自动侦测布局改变,需要自己手动计算、设置动画目标值。

4.2 Transition(过渡动画)

优点:

  1. 针对布局或场景切换时的过渡动画,使用非常高效、方便。只需写布局变化,无需关心具体动画细节。
  2. 系统自动处理视图的“初始状态”与“目标状态”,减少大量繁琐的动画计算代码。
  3. 有现成的 Fade, Slide, ChangeBounds 等常用过渡类型,也可以组合或自定义。

不足:

  1. 不适合“单个元素的精细动画”或“自定义属性动画”。Transition 更多是布局层面、场景层面的大范围切换。
  2. 相比属性动画,自定义空间相对较小(虽然也能写 Transition 子类,但门槛更高)。
  3. 如果只是做些旋转、弹跳、局部动效,使用 Transition 可能有些“大材小用”且不够灵活。

5. 具体使用示例对比

5.1 使用属性动画实现一个简单移动

// 属性动画移动示例
val view: View = findViewById(R.id.myView)
val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f)
animator.duration = 500
animator.start()
  • 直接改变 translationX 属性,使 view 在 0 ~ 300f 间移动。

5.2 使用 Transition 进行布局切换

// layoutA.xml -> layoutB.xml 过渡示例
TransitionManager.beginDelayedTransition(constraintLayout, AutoTransition())

// 例如,更改某个按钮的可见性
button.visibility = if (button.visibility == View.VISIBLE) View.GONE else View.VISIBLE

// 更改某个View的约束属性等
val params = myView.layoutParams as ConstraintLayout.LayoutParams
params.topMargin = 200
myView.layoutParams = params
  • 当调用 TransitionManager.beginDelayedTransition(...) 后,再修改布局属性,系统会自动对位置、大小、可见性变化做动画过渡。

6. 如何选择?

  1. 如果只是做一个(或几个)视图的微动效(如透明度、移动、旋转、缩放),并不涉及布局切换或多视图的同步动画,优先使用“属性动画(Property Animation)”。
  2. 如果要对整个布局或多个视图进行统一过渡(比如点击一个按钮后页面某几个区域同时从显示变隐藏,或从一个ConstraintSet切换到另一个ConstraintSet)而且想要自动过渡,则使用“Transition Framework”来简化操作。
  3. 如果需要同时兼顾布局切换一些复杂的自定义动画,就需要把 Property Animation 和 Transition 结合来用,或者在 Transition 中自定义 Transition 子类来处理更复杂的属性变换。

7. 为何说Transition 的实现本质上是属性动画?

1. Transition 本身不直接绘制动画

  • Transition 主要做的事是:

    1. 捕捉某些感兴趣的视图属性(如位置、大小、透明度等)的 开始值结束值
    2. 对比这些属性的差异,决定要对哪些属性执行动画及如何执行。
    3. 创建并管理这些动画对象的生命周期(开始、结束、取消等)。
  • Transition 并不直接完成对属性的“插值计算”或“实时绘制”。它最终会将属性的变更交给动画系统去插值、去更新,这个动画系统就是 属性动画Property Animation)。


2. Transition 内部通过创建 Animator(通常是 ObjectAnimator)实现动画

  • 无论是系统自带的 Fade / Slide / ChangeBounds 等过渡动画,还是自定义 Transition,它们在内部真正做动画时,都要通过 Animator 来改变视图属性。
  • Animator 的具体实现常见于 ObjectAnimator.ofFloat()ObjectAnimator.ofInt() 等方法,即标准的 属性动画 API

示例简析(以 Fade 为例):

java
复制代码
@Override
public Animator createAnimator(
        ViewGroup sceneRoot, 
        TransitionValues startValues, 
        TransitionValues endValues
) {
    if (startValues == null || endValues == null) {
        return null;
    }
    View view = endValues.view;
    float startAlpha = (float) startValues.values.get(PROPNAME_ALPHA);
    float endAlpha = (float) endValues.values.get(PROPNAME_ALPHA);

    // 如果 alpha 有变化,就创建一个 ObjectAnimator
    if (startAlpha != endAlpha) {
        view.setAlpha(startAlpha);
        return ObjectAnimator.ofFloat(view, View.ALPHA, startAlpha, endAlpha);
    }
    return null;
}

如上所示,过渡动画的核心正是 ObjectAnimator.ofFloat(...),它背后的执行逻辑就是“属性动画”在插值并更新 alpha 值,才实现了淡入淡出的过渡。


3. “捕捉→对比→动画” 这一套流程天然契合属性动画

Transition 的大体思路是:

  1. 捕捉初始属性值captureStartValues
  2. 捕捉结束属性值captureEndValues
  3. 对比初始值和结束值,生成对应的 AnimatorcreateAnimator

而属性动画的使用方式正是:

  • 设置动画的起始值和结束值
  • 在动画的每一帧,通过插值器插值,计算当前帧的属性值
  • 将属性值应用到对象上

两者逻辑上高度重合。


4. 自定义 Transition 依旧是通过自定义属性动画实现

如果需要自定义动画,比如让视图做旋转或自定义路径运动,你需要在自定义的 Transition 中:

  1. captureStartValuescaptureEndValues 中记录自定义的属性(如 rotation)。
  2. createAnimator 中根据属性变化创建 Animator,通常是 ObjectAnimator.ofFloat(view, "rotation", startRotation, endRotation)

这依旧使用了属性动画的 API 来进行插值和更新属性,也再次说明了 Transition 只是一个管理过渡动画的框架,而 真正驱动属性变化的仍是属性动画


5. Transition 框架只是对“视图变化场景”进行封装

  • 从使用角度看,TransitionManager.beginDelayedTransition() 会把视图层级的变化(添加、移除、Layout参数改变等)捕捉下来,并自动触发对应过渡动画。
  • 但一旦需要执行动画,它还是要去生成 Animator,而 Animator 的实质就是“属性动画”。

因此,Transition 并不是一个替换或摆脱“属性动画”概念的全新东西,而是基于属性动画之上的更高层封装,帮助开发者在布局发生变化时,更简洁、更自动化地完成动画过渡。


总结

Transition 的实现本质上是属性动画” ,指的就是:

  1. Transition 并不自行插值绘制,而是通过捕捉前后属性 → 生成 Animator → 交给系统属性动画运行。
  2. Transition 内部大量调用 ObjectAnimatorValueAnimator 这些属性动画 API,用于具体完成视图属性的过渡。
  3. 从原理到执行流程,都离不开属性动画的插值机制和属性更新机制。

所以,Transition 说到底是“在布局变化前后,利用属性动画去驱动视图属性变化”


8. 总结

  • 属性动画 专注于 某些属性值 随时间变化,可以对任何对象的任意字段应用动画,灵活但需要手动编写细节,适合微动效需要高度控制的场景。
  • Transition 专注于 界面布局状态 之间的切换,自动侦测变化,省去了手动计算,非常适合界面整体的过渡多视图同步的布局变化场景。
  1. 两套动画机制服务于两种不同层级的需求

    • Property Animation:更底层、更灵活,可应用于任何可更新数值的对象属性。强调的是“我想让这个值随时间变化到另一个值”。
    • Transition:更高层、更关注“从一个界面/布局状态过渡到另一个状态”,系统自动侦测哪里变了、需要怎么动。
  2. 它们各有用武之地

    • 如果只是移动/淡出/旋转某个单个 View,写属性动画更直观。
    • 如果要做大范围布局切换,让一堆 View 同步变化位置和可见性,用 Transition(或 MotionLayout)更省心。
  3. 动画效果类似≠开发思路相同

    • 最终看起来都是“会动的界面”,但实际实现原理、编写方式、可扩展性都不一样。
    • 因此 Android 提供了两套动画方案,满足不同层次的需求。

所以,并不是说“为什么要搞两套,合并成一个不行吗?” ——它们虽然都有“动画”二字,但本质针对的使用场景和思路是不同的。正因为场景不同,所以两套机制各自精简、各自强大,合在一起反倒会变得臃肿、难以维护。选择合适的工具来处理相对应的场景,才是最有效率的做法。