使用转场动效为布局变化添加动画效果

试用 Compose 方式
Jetpack Compose 是推荐在 Android 设备上使用的界面工具包。了解如何在 Compose 中使用动画。
<ph type="x-smartling-placeholder"></ph> AnimatedContent →

Android 的过渡框架可让您为各种运动添加动画效果, 通过提供起始布局和结束布局来设计界面。 您可以选择想要的动画类型,例如淡出视图 或更改视图尺寸,而过渡框架决定了 如何为从起始布局到结束布局添加动画效果。

过渡框架包含以下功能:

  • 群组级动画: 将动画效果应用于视图层次结构中的所有视图。
  • 内置动画: 对淡出或移动等常见效果使用预定义的动画。
  • 资源文件支持: 从布局资源文件加载视图层次结构和内置动画。
  • 生命周期回调: 接收可控制动画和层次结构的回调 更改流程

如需查看为布局变化添加动画效果的示例代码,请参阅 BasicTransition

在两种布局之间添加动画效果的基本流程如下所示:

  1. Scene 开始布局和结束布局不过,起始布局的场景是 通常是根据当前布局自动确定的。
  2. 创建 Transition 对象来定义所需的动画类型。
  3. 致电 TransitionManager.go()、 然后系统会运行动画来切换布局。

图 1 中的示意图说明了布局之间的关系 场景、转场和最终动画。

图 1. 基本图示 过渡框架创建动画的方式

创建场景

场景会存储视图层次结构的状态,包括其所有视图及其 属性值。过渡框架可以在开始播放之间运行动画, 和结束场景。

您可以通过布局创建场景 资源文件,或者从代码中的某个视图组获取。不过, 过渡的起始场景通常由 当前界面

场景还可以定义自己的操作,这些操作会在您进行场景转换时运行。 此功能可用于清理视图设置 过渡到场景的过程。

从布局资源创建场景

您可以直接从布局资源创建 Scene 实例 文件。当文件中的视图层次结构大部分是静态时,请使用此方法。 生成的场景表示在观看时,视图层次结构的状态 已创建 Scene 实例。如果更改视图层次结构 重新创建场景框架根据整个视图创建场景 文件层次结构。您无法从布局文件的一部分创建场景。

如需从布局资源文件创建 Scene 实例,请检索 从布局中获取场景根 ViewGroup。然后,调用 Scene.getSceneForLayout() 函数,其中包含场景根,以及需要创建该布局的 包含场景的视图层次结构。

定义场景布局

本节其余部分的代码段展示了如何创建两个 具有相同场景根元素的不同场景。这些代码段还展示了 您可以加载多个不相关的 Scene 对象,而不暗示 彼此相关。

该示例包含以下布局定义:

  • 具有文本标签和子项的 activity 的主布局 FrameLayout
  • 一个 ConstraintLayout,用于 包含两个文本字段的第一个场景。
  • 第二个场景的 ConstraintLayout,其中包含两个相同的文本字段, 不同顺序。

该示例经过精心设计,以确保所有的动画都发生在子视图内 activity 主布局的布局。主布局中的文本标签 会保持静态

Activity 的主布局定义如下:

res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/master_layout">
    <TextView
        android:id="@+id/title"
        ...
        android:text="Title"/>
    <FrameLayout
        android:id="@+id/scene_root">
        <include layout="@layout/a_scene" />
    </FrameLayout>
</LinearLayout>

该布局定义包含一个文本字段和 FrameLayout 场景根。第一个场景的布局包含在主布局文件中。 这样,应用便可以将其显示为初始界面的一部分,并且还会加载 因为框架只能将整个布局文件加载到 场景。

第一个场景的布局定义如下:

res/layout/a_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    
</androidx.constraintlayout.widget.ConstraintLayout>

第二个场景的布局包含两个相同的文本字段: ID 相同,但顺序不同。其定义如下:

res/layout/another_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    
</androidx.constraintlayout.widget.ConstraintLayout>

从布局生成场景

为两个约束布局创建定义后,您可以获取 场景。这样,你就可以在两个界面之间 配置。 如需获取场景,您需要引用场景根和布局 资源 ID。

以下代码段展示了如何获取对场景根的引用,并 从布局文件创建两个 Scene 对象:

Kotlin

val sceneRoot: ViewGroup = findViewById(R.id.scene_root)
val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this)
val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)

Java

Scene aScene;
Scene anotherScene;

// Create the scene root for the scenes in this app.
sceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Create the scenes.
aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this);
anotherScene =
    Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);

在应用中,现在有两个基于视图的 Scene 对象 层次结构。两个场景都使用由 res/layout/activity_main.xml 中的 FrameLayout 元素。

在代码中创建场景

您还可以从Scene ViewGroup 对象。在修改视图层次结构时使用此方法 也可以直接在您的代码中生成。

如需在代码中通过视图层次结构创建场景,请使用 Scene(sceneRoot, viewHierarchy) 构造函数。调用此构造函数等同于调用 Scene.getSceneForLayout() 函数。

以下代码段演示了如何创建 Scene 实例来自场景根元素和场景中场景的视图层次结构, 您的代码:

Kotlin

val sceneRoot = someLayoutElement as ViewGroup
val viewHierarchy = someOtherLayoutElement as ViewGroup
val scene: Scene = Scene(sceneRoot, viewHierarchy)

Java

Scene mScene;

// Obtain the scene root element.
sceneRoot = (ViewGroup) someLayoutElement;

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered.
viewHierarchy = (ViewGroup) someOtherLayoutElement;

// Create a scene.
mScene = new Scene(sceneRoot, mViewHierarchy);

创建场景操作

该框架允许您定义自定义场景操作,系统会在 进入或退出场景的方法。在许多情况下,定义自定义场景操作 没必要这样做,因为框架会为场景之间的变化添加动画效果 。

场景操作有助于处理以下情况:

  • 为不在同一层次结构中的视图添加动画效果。你可以为以下对象 使用退出和进入场景操作创建起始场景和结束场景。
  • 如需为过渡框架无法自动添加动画效果的视图添加动画效果, 例如 ListView 对象。有关 请参阅限制部分。

如需提供自定义场景操作,请将操作定义为 Runnable 对象,并将其传递给 Scene.setExitAction()Scene.setEnterAction() 函数。框架在开始播放时调用 setExitAction() 函数, 运行过渡动画和 setEnterAction() 之前的场景, 函数。

应用过渡

过渡框架表示具有 Transition 对象。您可以使用内置的Transition 子类,例如 AutoTransitionFade定义自己的过渡。 然后,您可以运行 通过传递结束 Scene 来切换场景之间的动画效果 并将 Transition TransitionManager.go()

过渡生命周期与 activity 生命周期类似, 框架在开始播放和结束播放之间监控的过渡状态, 动画结束。在重要的生命周期状态下,框架会调用 您可以实现这些回调函数,以调整 不同阶段。

创建过渡

上一部分介绍了如何创建场景来表示 不同的视图层次结构定义起始场景和结束场景后 创建一个用于定义动画的 Transition 对象。 借助该框架,您可以在资源文件中指定内置过渡 并在代码中对其进行膨胀,或者创建一个内置转场效果的实例 直接在代码中实现

表 1. 内置过渡类型。

标记 影响
AutoTransition <autoTransition/> 默认过渡。按该顺序淡出视图、移动视图、调整大小以及淡入视图。
ChangeBounds <changeBounds/> 移动视图并调整其大小。
ChangeClipBounds <changeClipBounds/> 拍摄场景前后的 View.getClipBounds() 并在过渡期间为这些更改添加动画效果。
ChangeImageTransform <changeImageTransform/> 捕获场景前后的 ImageView 矩阵 并在过渡期间为其添加动画效果
ChangeScroll <changeScroll/> 捕获场景前后目标的滚动属性 并为所有变化添加动画效果
ChangeTransform <changeTransform/> 捕获场景切换前后视图的缩放和旋转 并在过渡期间为这些更改添加动画效果。
Explode <explode/> 跟踪开始和结束时间的目标视图可见性的变化 并将视图从场景边缘移入或移出。
Fade <fade/> fade_in 淡入视图。
fade_out 会淡出视图。
fade_in_out(默认)会执行 fade_out,后跟 fade_in
Slide <slide/> 跟踪开始和结束时间的目标视图可见性的变化 并将视图从场景的其中一个边缘移入或移出。

从资源文件创建过渡实例

通过此方法,您可以修改过渡定义,而无需更改 代码。这种方法也有助于将复杂的 定义转换,如 了解如何指定多个过渡效果

如需在资源文件中指定内置过渡,请按以下步骤操作:

  • res/transition/ 目录添加到项目中。
  • 在此目录中新建一个 XML 资源文件。
  • 为其中一个内置过渡添加 XML 节点。

例如,以下资源文件指定了 Fade 过渡:

res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

以下代码段展示了如何膨胀 Transition 内的 从资源文件获取 activity:

Kotlin

var fadeTransition: Transition =
    TransitionInflater.from(this)
                      .inflateTransition(R.transition.fade_transition)

Java

Transition fadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

在代码中创建过渡实例

如果您要动态创建转场对象, 在代码中修改界面并创建简单的内置转场 参数很少或没有参数的实例。

如需创建内置过渡的实例,请调用某个公开过渡 Transition 类的子类中的构造函数。例如, 以下代码段会创建一个 Fade 过渡的实例:

Kotlin

var fadeTransition: Transition = Fade()

Java

Transition fadeTransition = new Fade();

应用过渡

通常情况下,您可以在不同视图层次结构之间应用转场效果, 对事件(例如用户操作)的响应。以一款搜索应用为例: 当用户输入搜索字词并点按搜索按钮时,应用会 呈现结果布局的场景,同时应用 让搜索按钮淡出并淡入搜索结果。

要在应用转场效果以响应以下活动中的事件时更改场景 调用 TransitionManager.go() 类函数,并以 用于动画的场景和转场实例,如 以下代码段:

Kotlin

TransitionManager.go(endingScene, fadeTransition)

Java

TransitionManager.go(endingScene, fadeTransition);

该框架会使用视图更改场景根内的视图层次结构 在运行由 过渡实例。起始场景是最后一个场景的结束场景 过渡效果。如果之前没有过渡,则确定起始场景 自动从界面的当前状态自动恢复。

如果您未指定过渡实例,过渡管理程序可以将 自动转换,这种转换可以执行适合大多数情况的合理操作。对于 请参阅 API 参考,了解 TransitionManager 类。

选择特定的目标视图

该框架会将转场应用于起始场景和结束场景中的所有视图 默认情况。在某些情况下,您可能只想将动画应用到其中一部分 场景下的视图通过该框架,您可以选择 设置动画效果。例如,该框架不支持为 ListView 对象,因此不要尝试在过渡期间为它们添加动画效果。

过渡添加动画效果的各个视图称为目标。您只能 选择属于与场景关联的视图层次结构的目标。

要从目标列表中移除一个或多个视图,请调用 removeTarget() 方法,然后再开始转换。要仅将您指定的视图添加到 目标列表之后,调用 addTarget() 函数。有关详情,请参阅 Transition 类。

指定多个过渡

为了让动画发挥最大效果,请确保动画与更改类型相配 场景切换时会发生什么例如,如果您要移除一些视图 淡出或淡入动画会带来明显的 这表示某些视图不再可用如果您要将数据视图 则最好以动画方式呈现动画效果 用户会注意到视图的新位置。

您不必只选择一个动画,因为转场框架 可让您在转换集中组合动画效果,该转换集中包含一组 单独的内置或自定义转场效果。

要从 XML 中的过渡集合定义一个过渡集,请创建一个 res/transitions/ 目录中的资源文件,并列出其下的转场效果 TransitionSet 元素。例如,以下代码段展示了如何 请指定与 AutoTransition 具有相同行为的过渡集 类:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
</transitionSet>

要将过渡集膨胀到 TransitionSet 对象位于 您的代码,调用 TransitionInflater.from() 函数。TransitionSet 类从 Transition 类,因此您可以将其与转换管理器结合使用,就像使用其他任何类一样 其他 Transition 实例。

应用没有场景的过渡

更改视图层次结构并不是修改界面的唯一方式。您 还可以通过添加、修改和移除子视图 当前层次结构

例如,您可以使用 一种布局先使用显示一个搜索输入字段和一个搜索的布局 图标。若要更改界面以显示结果,请移除搜索按钮 调用 ViewGroup.removeView() 函数,并通过调用 ViewGroup.addView() 函数。

如果备选方案是有两个层次结构, 几乎完全相同。无需创建和维护两个单独的布局文件 您可以创建一个布局文件 包含您在代码中修改的视图层次结构。

如果您以这种方式在当前视图层次结构中进行更改,则不会 我们需要创建一个场景。不过,您可以创建并应用 使用延迟过渡的两种视图层次结构状态。 过渡框架从当前的视图层次结构状态开始, 对视图所做的更改,并应用转场,以动画效果 在系统重新绘制界面时发生变化。

如需在单个视图层次结构中创建延迟过渡,请按以下说明操作 步骤:

  1. 当触发转换的事件发生时,调用 TransitionManager.beginDelayedTransition() 函数,提供所有视图的父视图 以及要使用的转场效果框架会存储当前的 子视图的状态及其属性值。
  2. 根据用例的要求更改子视图。框架 记录您对子视图及其属性所做的更改。
  3. 当系统根据您的更改重新绘制界面时, 框架可为原始状态与新状态之间的变化添加动画效果。

以下示例展示了如何为向视图添加文本视图添加动画效果 使用延迟过渡的层次结构。第一个代码段展示了 定义文件:

res/layout/activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <EditText
        android:id="@+id/inputText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>

下一个代码段展示了为添加文本视图添加动画效果的代码:

MainActivity

Kotlin

setContentView(R.layout.activity_main)
val labelText = TextView(this).apply {
    text = "Label"
    id = R.id.text
}
val rootView: ViewGroup = findViewById(R.id.mainLayout)
val mFade: Fade = Fade(Fade.IN)
TransitionManager.beginDelayedTransition(rootView, mFade)
rootView.addView(labelText)

Java

private TextView labelText;
private Fade mFade;
private ViewGroup rootView;
...
// Load the layout.
setContentView(R.layout.activity_main);
...
// Create a new TextView and set some View properties.
labelText = new TextView(this);
labelText.setText("Label");
labelText.setId(R.id.text);

// Get the root view and create a transition.
rootView = (ViewGroup) findViewById(R.id.mainLayout);
mFade = new Fade(Fade.IN);

// Start recording changes to the view hierarchy.
TransitionManager.beginDelayedTransition(rootView, mFade);

// Add the new TextView to the view hierarchy.
rootView.addView(labelText);

// When the system redraws the screen to show this update,
// the framework animates the addition as a fade in.

定义过渡生命周期回调

过渡生命周期与 Activity 生命周期类似。它代表 框架在通话之间监控的过渡状态 TransitionManager.go() 函数,并将完成 动画。在重要的生命周期状态下,框架会调用回调 由 TransitionListener 定义 界面。

过渡生命周期回调非常适用于复制视图等场景 属性值从起始视图层次结构到结束视图层次结构 在场景转换期间出现您不能简单地将初始视图中的值复制到 是结束视图层次结构中的视图,因为结束视图层次结构 直到转换完成为止。相反,您需要将 然后将其复制到结束视图层次结构 已完成转换。如需在转换完成时收到通知, 实施 TransitionListener.onTransitionEnd() 函数。

有关详情,请参阅 TransitionListener 类。

限制

本部分列出了过渡框架的一些已知限制:

  • 将动画应用到 SurfaceView可能不会显示 正确。SurfaceView 实例是从非界面线程更新的,因此 update 可能与其他视图的动画不同步。
  • 某些特定过渡类型可能无法产生所需的动画效果 应用于 TextureView 时。
  • 扩展的类 AdapterView,例如 ListView, 以不支持的方式管理子视图, 过渡框架如果您尝试基于 AdapterView 为视图添加动画效果, 设备显示屏可能会停止响应。
  • 如果您尝试调整TextView 动画时,在对象完全加载前,文本会弹出到新位置 大小。为避免此问题,请勿在调整包含 文本。