现在能用 ConstraintLayout 做些什么?

Nicolas 在谷歌负责过许多项目,比如安卓浏览器的 HTML5 支持,Hoenycomb 的 webview 硬件加速,还有在 JB,KitKat 上的可扩展的非相干性的图片编辑器。然后他做了一年的机器人高速检测系统,之后他又回到安卓开发,实现了 ConstraintLayout,现在他负责安卓开发工具的布局编辑器。

介绍 (0:00)

我在谷歌工作,在我工作期间,我负责安卓框架。

在过去的一年半里,我负责 ConstraintLayout。我主要的工作是开发工具,但是也有一些研发的经验。我的 ConstraintLayout 的目标就是为了帮助开发者构建一些新的东西。

为什么 ConstraintLayout? (0:45)

如果你记得去年五月的 Google I/O,我们发布了一个特别酷的编辑器,布局编辑器。我实现 ConstraintLayout 原因就是想把它和编辑器联系起来。

我想要实现一个新的布局编辑器。一个非常强大而且容易使用的编辑器。为了实现这个目标,我感觉我需要一个更强大而且更灵活的 ViewGroup - 一个能更好地使用 ID 的 ViewGroup。结果就出现了 ConstraintLayout,它十分灵活。它允许你非常容易地创建扁平结构,这是一件好事情。

ConstraintLayout 也好似一个非捆绑式函数库,这个概念对于开发者和像我们这样的库的创建者来说,都是非常重要的概念。这对你很有用,因为你可以控制所有的事情。当你读一些东西的时候,你能立马得到它而且你可以自行决定是否使用。这不是推送给你的。你可以测试,而且一旦你有一个最好的版本,你就可以发布它。如果过两天我们又发布了另一个新的版本,你不会受到任何影响。

对我们而言这也很好,这是因为下面这个原因。我们可以推送一个新的版本。我们不用担心这会破坏任何事情。这意味着我们可以有些时候清除一些 API 或者增加一些新功能。最后,因为它是非绑定的,任何设备都能够移植它。

RelativeLayout (2:48)

RelativeLayout 是你过去可能常用的 Layout,在平面上,它和 ConstraintLayout 有许多类似的地方。你可以相对地放置你的子视图,这第一眼看起来和 ConstraintLayout 实现的一样。RelativeLayout 有一些你可能早就知道的问题。

这是一个完全人为的例子,但是我需要这个按钮,它一直都需要保持在容器的左下方。


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://
schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent">
	<Button
		android:id="@+id/button6"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_alignParentBottom="true"
		android:layout_alignParentEnd="true"
		android:layout_alignParentRight="true"
		android:layout_marginBottom="34dp"
		android:layout_marginEnd="24dp"
		android:layout_marginRight="24dp"
		android:text="Button" />
</RelativeLayout>

当你设置你的容器高度上容下所有的内容的时候,会发生什么?


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://
schemas.android.com/apk/res/android"
		android:layout_width="match_parent"
		android:layout_height="wrap_content">
		<Button
			android:id="@+id/button6"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignParentBottom="true"
			android:layout_alignParentEnd="true"
			android:layout_alignParentRight="true"
			android:layout_marginBottom="34dp"
			android:layout_marginEnd="24dp"
			android:layout_marginRight="24dp"
			android:text="Button" />
</RelativeLayout>

按钮在底部,因为这就是 RelativeLayout 的工作方式。但是这不是我期望的行为。

如果我们在 ConstraintLayout 做同样的事情,但你调用 wrap content,它能正常工作。


<?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"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="wrap_content">

	<Button
		android:id="@+id/button12"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Button"
		app:layout_constraintBottom_toBottomOf="parent"
		android:layout_marginBottom="34dp"
		android:layout_marginEnd="24dp"
		app:layout_constraintRight_toRightOf="parent"
		android:layout_marginRight="24dp" />
</android.support.constraint.ConstraintLayout>

你可以也看看这些 XML,和 RelativeLayout 比起来,结果好很多。事实上,唯一新增的两个属性就是那两个。它们在这里确保了在底部和在父容器的右部的限制。其他的在 XML 里面的属性都是你熟知的属性,比如 height, margin, 还有 cetera。

嵌套 Layout (4:34)

你可以创建一个扁平结构,这里你可以想象布局不是非常复杂。你有一个水平的和一组垂直的布局,让我们假设它们是 TextViews。

假设 TextView 的高度变化了,例如,父亲布局需要测量。这意味着我们需要重新测量所有的元素,这目前看来是非常快的因为它们是缓存的。但是你需要从父视图开始测量,然后这个,然后那个,直到所有的都完成。

我们需要关注布局速度的原因是你需要在 15 毫秒内完成你的布局更新,这样 UI 才会流畅。而且如果你在 frame 里面有许多变化的话,就会导致许多这样的计算。结构越深,情况就会爆炸式恶化,这样你就会有许多的 measure pass 了。

layout pass 和 measure pass: 当你知道组件的尺寸和位置的时候,布局就会发生。测量是要求你控制的这些所有的不同组件来测量自己。这开销非常大。如果你在测量一个 TextView,你需要布局整个文字段。测量的过程在某些方面比布局开销还要大。我真的鼓励你测量你的布局来确定你不会超过它们的限制。

关于工具的担心 (8:14)

你可能会有些担心,比如,关于性能。目前,如果内容相似的话,它和 RelativeLayout 差不多,而且当我在 ConstraintLayout 采用同样的布局的话 - 性能将一样或者快点。

如果你打算完成一个扁平结构的话,你可以得到最多的好处。如果你发现一个 ConstraintLayout 很慢的例子,请发给我们。我非常自信能使得它更快些,因为有许多的优化可以做。

测量的事情怎么办?它和 RelativeLayout 类似,当然 RelativeLayout 会变慢。我们告诉人们它会变慢因为它总是会有两次 layout passes。在我们的例子里,我们不这样做;我们采用一个设置来代替。这意味着如果你想包含内容,有设置尺寸的控件会只做一次控件的 measure pass。如果你有一个适配限制,我们会做两次 measures passes。

这是怎么工作的呢?一开始,我们有线性方程解。这是你指定的线性方程在某种错误概率下的答案。你把它代入解中,解就会告诉你答案。

关于解,我们其实做了些微小地调整。我们知道直接修复。因为我们有限制的模式,我们能在许多的情况下,确认以前我们能够直接解决的情况。而且在这个条件下,我们真的双倍或者三倍加快了剩下系统的性能。

我们开发布局编辑器的原因是我们认为我们能做些事情让你更迅速地完成 XML 的编写。换句话说,如果你熟悉,写 XML 是件非常快的事情。XML 不会消失,你还是需要 XML。而且,你可以在 XML 里面使用 ConstraintLayout,这不比使用 RelativeLayout 复杂多少。

边际限制 (14:45)

采用边际限制,你可以说明你将移动一个控件的边沿到一个目标。这和 RelativeLayout 类似。你也有一个用作对齐的基准。基准就是文字的底部,所以你可以对齐两个不同大小的按钮,但是文字会垂直对齐。在 XML 里面实现也是非常容易的:你开始于布局的限制,这里你开始限制和决定你需要给目标的限制。

在这样的情况下,约束所做的事情是,它会有边际,这些边际是你在同样的地方对你的目标的限制。你可以使用元素的 ID 作为目标,也可以使用关键字 Parent。你和 Parent 数据相连,而且它会自动使用 Parent。

中心限制 (16:15)

ConstraintLayout 的另外一个优点就是 中心限制。如果你给同样的控件设置左边和右边的限制会发生什么?它们不可能同时正确。

app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

我们在这个例子里面想做的事情是居中。在这个例子里面,不带偏见,默认值都是 50%,这会使得控件居中。你可以移动它,然后把它改成其他的东西,这会使得你构建一个更具有扩展性的 UI。

还有 尺寸限制。你描述一个固定尺寸或者使用包含内容来获取控件自己的尺寸。但是不要使用 match parent,因为这是不支持的,我们使用 Match Constraint。你可以指定一个比率,再给 ConstraintLayout 加上一个最大的高度和宽度。

Match Constraint. Match Constraint 意味着替代 Wrap Content 或者一个固定尺寸,你使用 zero DP,这和线性 layout 里面用法类似。这样的结果就是它会找到控件的尺寸来符合那些限制条件。如果你的控件是 Wrap Content,通过设置 Match Constraint,它就会扩大。你可以指定 margin,这样它就会比 margin 的尺寸略小一些。


app:layout_constraintDimensionRatio="h,1:1"

关于比率,你唯一需要做的事情就是给 Match Constraint 设置一个尺寸。当你想使用解给你的尺寸的时候,采用 zero DP 并且指定比率。你应该指定这个属性,和 Match Constraint 的一个尺寸。

GONE 和 Chains (19:29)

许多人不知道 View.GONE。当你有 View A 和 受制于 A 的 View B 的时候,我们想给 View A 设为 GONE。以下事情将会发生:当 A 被移除的时候,它还是会被方程使用。A 仅仅变成了一个单点,将会移出边界。你的 A 和 B 之间的留白变成了和 A 还在那里的时候一样,A 没有消失。


app:layout_goneMarginLeft="60dp"

你可以给某些东西指定一个 GONE Margin,而且指定你想要的任何值。在这个例子里,当你想限制的目标被标为 GONE 的时候。一个最近增加的强大功能是 Chains。一个 chain 会被认为是一个双向的限制。在至少两个控件中间的相反方向的限制。


<Button
	android:id="@+id/button1"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="Button"
	tools:layout_editor_absoluteY="10dp"
	app:layout_constraintRight_toLeftOf="@+id/button2"
	app:layout_constraintLeft_toLeftOf="parent" />
<Button
	android:id="@+id/button2"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="Button"
	tools:layout_editor_absoluteY="10dp"
	app:layout_constraintRight_toRightOf="parent"
	app:layout_constraintLeft_toRightOf="@+id/button1" />

你在这个 XML 里面实现的方式不是非常直观,但是你可以把它看作从右到左的按钮。左边是在父亲上的,而且你右边也是在父亲上的,但是左边接着右边的另一个按钮。你可以看到这是双向反向的限制。

限制你有一个 chain,你可以做些有意思的事情。首先,在 chain 开始的地方,如果你设置具体的属性,这会导致 chain 的行为。那些是我们支持的 chain 的风格。简单地创建你的 chain 然后指定风格,比如 spread, spread inside, widget all packed, 你会得到不同的结果。

控件和指定的风格的行为有些不同,控件会打开。在这种情况下,B 和 C 是 zero DP 而且默认的行为是平分空间。你可以让一个控件比另外一个获得的空间多些。

一个 chain 的风格是封装好的。你可以应用 bias,这样你能够把 chain 看作 Linear Layout 里面的 Group 类似的东西。不同之处就是它给了你在类似轴上的行为。这使得你可以实现那些现在或者以前使用类似 Linear layout 来构建的 UI。


app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_chainStyle="packed"

Guidelines (22:53)

Guidelines 的想法是让我们可以有些东西帮助你创建 UI。而且你可能认为它和 Editor 帮助你的方法类似,但是实际上它们不能帮助你完成最后的 View 结构。在下面这个 Guideline 的例子中,透传后它就消失了,而且都展开。这里是个 XML 的例子。


<android.support.constraint.Guideline
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:id="@+id/guideline"
	app:layout_constraintGuide_begin="20dp"
	android:orientation="vertical" />

ConstraintSet (24:12)

最后我想说的事情就是 Constraint Layout 里的 ConstraintSet。


ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint Set
ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set

mConstraintSet2.clone(context, R.layout.state2); // get constraints from layout
setContentView(R.layout.state1);
mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main);
mConstraintSet1.clone(mConstraintLayout); // get constraints from ConstraintSet

TransitionManager.beginDelayedTransition(mConstraintLayout);
mConstraintSet1.applyTo(mConstraintLayout

这里的想法是你有这个扁平的布局。扁平布局的一个很棒的地方就是使得对齐容易了很多。如果你做些动画,或者在很深的嵌套布局中使用动画,你会有各种各样的问题。如果你有扁平的设计,线性动画会效果更好。

ConstraintSet 使你能够更进一步。他能够做的事情就是抓住一组限制,然后你能够保持这些数据。

展示 (26:42)

我可以展示一个按钮,然后拖动它。首先你需要注意的事情就是我能够把它放到任何位置。限制,它不受限制。事实上,如果我打开 XML 文件,你可以看到 Editor 正在抱怨它不受限制。

你可能还听说过 blueprint 模式。你能想象这个 blueprint 模式就好像 x-ray 模式。它接受你当前的 view 然后给你显示相关的布局。某种程度上,它就是 xml 属性的视图模式。而且是一一对应的。

我个人很喜欢 blueprint 模式因为我想它可以让基本的事情更加清楚些。你可以使用设计模式。默认情况下,我们不显示限制,除非你在上面停留。

我希望你们能得到这个工具的背景知识。使用它肯定不止一种方法。我们尽力想为你,按照你的想法使用这个工具,提供不同的路径和体验。

如果你真的是一个 XML 的高效使用者,你可以继续使用 XML 而且你可以在这里预览。预览框不仅仅是一个预览框,它还是一个成熟的编辑器,使你能够改变些东西。

下一步?/ 结论 (36:33)

接下来是快速一览。我们当然希望实现更多的灵活性。如果你使用 Conversion 工具,它使得你能够使用老的布局文件,而且转换它。

请开 bug,联系我,不用犹豫。


Nicolas Roard

Nicolas Roard

Nicolas worked over the years on various projects at Google, from the HTML5 support in Android Browser, implementing the webview hardware acceleration in Honeycomb, or writing a scalable and non-destructive photo editor in JB/KitKat. After a year building high-speed telemetry systems for robots, he came back to Android, developed ConstraintLayout and now works on the Android Studio layout editor.

Transcribed by Hilary Fosdal
Edited by Will Ha