赶紧给你的 layout 变化加上动画吧!
什么是给 layout 变化加上动画?
其实很好理解,就是在 layout,也就是我们说的布局内容发生变化的时候,给它添加上一个过渡的动画,使其看起来更显得自然一些。
其实 Android 自带的 framework 已经为我们提供了这么一个能力,它可以在两个 layout 变化的时候加上动画,使 layout 的变化更加自然。这么好的功能为什么不用呢?而且实际上,它的使用也并不复杂,只需短短的几行代码,就可以提升用户的操作体验,何乐而不为。
about transition framework
- Group-level animations: Apply one or more animation effects to all of the views in a view hierarchy.
- Built-in animations: Use predefined animations for common effects such as fade out or movement.
- Resource file support: Load view hierarchies and built-in animations from layout resource files.
- Lifecycle callbacks: Receive callbacks that provide control over the animation and hierarchy change process.
根据官网文档所示,transition framework 有几个重要的特性:
- 组级别动画:支持运用一个或多个动画到视图层级上。
- 内建动画:framework 已经为我们提供了很多的内置动画,比如你可以使用淡入淡入、位移动画等。
- 支持资源文件:构建场景 scene 时,支持资源文件直接导入,当然,同时它也是支持 view 导入的。
- 生命周期回调:可以监听到动画执行的声明周期,提供 start、resume、pause 等。
那么,transition framework 是怎么如何实现 layout 变化动画效果的?layout、scene、transition 他们之间的关系又是什么样的?我们看一张官方贴图。

首先,我们将起始 layout 以及结束 layout 转化成相对应的场景。
然后,创建想要执行的 transition。
最后,通过 TransitionManager 将场景和 transition 绑定在一起执行。
这样三步,就可以实现从场景一到场景二的动画转变了。
如何给 layout 变化加上动画

今天就以这个为例子说一下如何给 layout 变化加上平滑的动画。
从图中可以看到,点击机器人的时候,layout 改变了,机器人变大,并且名字也移动到右边,同时,详情介绍也出现了。返回的时候,layout 中的元素也平滑的回到之前的状态。这就是用 scene 和 transition 实现的。
1. 创建场景 scene
- 创建 layouts
layout_first.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/layout_container"
android:layout_width="wrap_content" android:layout_height="wrap_content">
<ImageView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:src="@mipmap/ic_launcher"
android:id="@+id/image_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
app:layout_constraintLeft_toRightOf="@id/image_icon"
app:layout_constraintTop_toTopOf="@id/image_icon"
app:layout_constraintBottom_toBottomOf="@id/image_icon"
android:layout_marginLeft="16dp"
android:textSize="20sp"
android:id="@+id/text_name"
android:text="头像"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
layout_second.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_marginTop="20dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:minHeight="200dp"
android:minWidth="200dp"
android:src="@mipmap/ic_launcher"
android:id="@+id/image_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
app:layout_constraintLeft_toLeftOf="@id/image_icon"
app:layout_constraintRight_toRightOf="@id/image_icon"
app:layout_constraintTop_toBottomOf="@id/image_icon"
android:layout_marginTop="16dp"
android:textSize="28sp"
android:id="@+id/text_name"
android:text="头像"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text_detail"
android:layout_marginTop="20dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_name"
android:layout_width="wrap_content"
android:textSize="18sp"
android:padding="16dp"
android:text="这是详情这是详情这是详情这是详情
这是详情这是详情这是详情这是详情这是详情这是详情
这是详情这是详情这是详情这是详情这是详情这是详情
这是详情这是详情这是详情这是详情这是详情这是详情
这是详情这是详情这是详情"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
注意:这里两个场景 layout 的相应控件的 id 需要设置成一样的。
activity.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LayoutWithSActivity">
<FrameLayout
android:id="@+id/layout_container"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="0dp">
</FrameLayout>
</android.support.constraint.ConstraintLayout>
- 根据 layout 生成相应的 scene
生成 scene 的方式有两种,可以通过 view 生成一个场景 scene,也可以直接加载布局文件生成 scene。
通过布局文件直接生成 scene
val sceneFirst = Scene.getSceneForLayout(layout_container, R.layout.scene_first, this)
val sceneSecond = Scene.getSceneForLayout(layout_container, R.layout.scene_second, this)
通过 view 直接生成 scene
val firstView = layoutInflater.inflate(R.layout.scene_first, null)
val secondView = layoutInflater.inflate(R.layout.scene_second, null)
val sceneFirst = Scene(layout_container, firstView)
val sceneSecond = Scene(layout_container, secondView)
第一种方式方便,直接加载布局文件就可以了,但是不易控制里面的元素。拿本例来说,因为要控制文字的改变,所以采取第二种通过 view 的方式来生成。
2. 创建 transition
创建 transition 有两种方式,可以在 xml 中定义,也可以只直接在代码中定义。
- 在 xml 中定义 transition
在 res/transition 下定义 transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds/>
<fade/>
<slide android:slideEdge="right"/>
</transitionSet>
transitionSet 是一个组合,可以同时定义多个 transition
然后再代码中加载:
val inflateTransition = TransitionInflater.from(this).inflateTransition(R.transition.layout_transform)
- 在代码中直接生成
al transition = ChangeBounds()
内置的 transition 大概有这么多个:

AutoTransition 是默认的transition,Fade out,move and resize,and fade in views。
ChangeBounds 改变边界,栗子中我们用的就是这种。
其他的大家自己可以去查看下源码。
2. 应用 transition
应用 transition 非常的简单
TransitionManager.go(scene, transition)
在栗子中,点击机器人就会转变到场景 2,并且文字也发生变化,那么我们可以这么写
firstView.setOnClickListener {
secondView.findViewById<TextView>(R.id.text_name).text = "从前置过来"
TransitionManager.go(sceneSecond, transition)
}
One More Thing
事实上,还有一种,不需要借助 scene 来实现的变化,那就是通过TransitionManager.beginDelayedTransition()。在执行TransitionManager.beginDelayedTransition()之后,系统会保存一个当前视图树状态的场景,之后当我们改变了View的属性之后(比如重新设置了View位置、缩放、clipe等等)。在下一次绘制时,系统会自动对比之前保存的视图树,然后执行相应动画。

实现的代码很简单
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_transiton_without_s)
val inflateTransition = TransitionInflater.from(this).inflateTransition(R.transition.layout_transform)
btn_add.setOnClickListener {
val text = TextView(this).apply {
text = "I am new"
}
TransitionManager.beginDelayedTransition(layout_items_container, Fade())
layout_items_container.addView(text)
}
btn_remove.setOnClickListener {
TransitionManager.beginDelayedTransition(layout_items_container, inflateTransition)
if (layout_items_container.childCount == 0) return@setOnClickListener
layout_items_container.removeViewAt(0)
}
}
写在最后
文中若有表述不恰当的地方,请合理指出,共勉。