引言
去年在 Google I/O 大会上Google上发布了 ConstraintLayout,使得我们在构建复杂布局的同时能够让视图层级得到精简,而且可以通过布局工具拖拽轻松实现布局,不过这必须得Android Studio 2.2以后才有这个工具,但ConstraintLayout这个类本身是兼容到Android 2.3 的,最新版的Android Studio2.3.x版本已经默认把ConstraintLayout作为布局的根节点了,所以有必要总结下,认真学习了之后才发现真的很强大,我相信你真的看了这篇文章的话也会认为的。
一、ConstraintLayout概述
ConstraintLayout约束布局和其他布局容器一样,都是继承自ViewGroup的,所以他也拥有其他布局的一些公用属性,与其他布局不同的是他是通过约束规则来实现布局的,所以他还新增了一些他特有的属性(后面再详说),虽然是在Android Studio2.2之后才有这个工具,但是向下兼容到Android版本2.3,官网中对它的描述就一句话:它允许您以灵活的方式定位和设置小部件。主要体现在可以以拖拽的方式来定义约束规则从而实现复杂的布局,当然你也可以通过原始的方式自己设置对应的属性。
二、ConstraintLayout所支持的约束类型
ConstraintLayout 的核心基础就是创建约束。约束定义了布局内两个组件之间的关系,从而控制组件的布局位置。ConstraintLayout所支持的约束类型目前一共支持7中类型的约束:相对定位Relative positioning、外边距Margins、中心定位Centering positioning、可见性Visibility behavior、具体尺寸约束Dimension constraints、链式约束Chains、虚拟助手对象Virtual Helpers objects,值得注意的是目前还未支持在约束中具有循环依赖关系,设置布局有两种方式可以手拖拽也可以在XML文件中书写,本质都是一样的,手拖拽的方式只不过是自动生成对应的属性,约束通常是以app:layout_constraintXxxxx为开头的属性。
1、相对定位Relative positioning和中心定位Centering positioning
相对定位Relative positioning和中心定位Centering positioning是将给定的子View相对于另一个子View在水平和垂直轴上约束,其实和RelativeLayout差不多。简单的理解为把其中一个子View当成参照物,另一个参照它布局,对应的属性。
- layout_constraintLeft_toLeftOf
- layout_constraintLeft_toRightOf
- layout_constraintRight_toLeftOf
- layout_constraintRight_toRightOf
- layout_constraintTop_toTopOf
- layout_constraintTop_toBottomOf
- layout_constraintBottom_toTopOf
- layout_constraintBottom_toBottomOf
- layout_constraintBaseline_toBaselineOf
- layout_constraintStart_toEndOf
- layout_constraintStart_toStartOf
- layout_constraintEnd_toStartOf
-
layout_constraintEnd_toEndOf
2、外边距Margins和可见性Visibility behavior
在常见的布局中如果一个子View可见状态为GONE时,设置外边距的时候就自动忽略掉了,而在ConstraintLayout中,除了共有的外边距属性之后还多了特有的 属性支持GONE状态的子View。
- layout_goneMarginStart
- layout_goneMarginEnd
- layout_goneMarginLeft
- layout_goneMarginTop
- layout_goneMarginRight
- layout_goneMarginBottom
3、具体尺寸比约束Dimension constraints
我们需要创建一些固定方向比的 View 组件,最常使用固定横纵比(即View 的宽度与高度的比例 w:h )的就是当 ImageView 用于展示一些固定横纵比的图片的时候,以前可以通过创建继承于 ImageView 的子类,并通过覆写 onMeasure() 来实现固定横纵比的布局。常用的 support library 中的 PercentLayout 也提供了一些机制来结局这类横纵比问题。而ConstraintLayout 也提供了机制来专门解决这个问题,选择想要控制横纵比的 View 然后通过属性视图中修改 ratio 值来动态适应
如上图,我们设置的 View 组件有着向父组件的 start 和 top 边缘的约束,它的 end 边缘则约束向一条参考线,而 bottom 边缘则没有被约束,这个 View 的 layout_width 和 layout_height 都被设置成 match_constraint,表示他们会根据所有的约束来设置宽高。在布局阶段这个组件的宽度就被计算好了,但是它的高度好像没有被确定。然后,因为设置了宽高横纵比,高度其实也被确定了,只是宽度的一个函数输出值(在以上例子中横纵比是 15:9 )这样设置的好处就是,当宽度变化的时候,高度自动跟着变化,如下图通过移动这个 View 组件 end 边缘约束向的参照线就可以看到效果。
<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="match_parent"
tools:context="com.stylingandroid.scratch.MainActivity">
<View
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintDimensionRatio="h,15:9"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.39" />
</android.support.constraint.ConstraintLayout>
设置横纵比的属性是 app:layout_constraintDimensionRatio ,而这个值有两个部分组成:方向和比例值。当宽高属性被设置成 match_constraint 实际上在 XML 源码中表现是被设置成 0dp,这就像 LinearLayout 的 weight 属性一样,会在 XML 中设置为 0dp ,而实际大小会根据父组件在布局 layout 过程中的大小来决定计算出来。
4、Bias用于控制控件在水平和垂直方向在屏幕上的偏移比例
- layout_constraintHorizontal_bias
- layout_constraintVertical_bias
当为目标子View设置好横纵向的约束时(app:layout_constraintLeft_toLeftOf=”parent”、app:layout_constraintRight_toRightOf=”parent”或者app:layout_constraintTop_toTopOf=”parent”、app:layout_constraintBottom_toBottomOf=”parent”),这个两个属性才会生效。实际操作过程中,你会发现对着设置好横纵向约束的Button进行拖动,布局中的layout_constraintHorizontal_bias和layout_constraintVertical_bias会一直发生相应的变化,如果你需要Button居中,那么直接将这两个属性的参数值设置为0.5即可。
5、layout_constraintDimentionRatio设置子View的宽和高按某个比例进行布局
值得注意的是这个属性layout_constraintDimentionRatio生效的条件必须满足:目标子View的layout_width和layout_height至少有一个设置为0dp,layout_constraintDimentionRatio默认参数比例是指宽:高;而变成高:宽可以设app:layout_constraintDimensionRatio=”H,2:1”,例如 app:layout_constraintDimensionRatio="H,3:1"
6、链式约束Chains
Chain 链是一种特殊的约束让多个 chain 链连接的 Views 能够平分剩余空间位置。在 Android 传统布局特性里面最相似的应该是 LinearLayout 中的权重比 weight ,但 Chains 链能做到的远远不止权重比 weight 的功能。链式约束主要是用于设置一组子View的布局,当某一个子View设置了
layout_constraintHorizontal_chainStyle 或者layout_constraintVertical_chainStyle 属性(水平垂直方向的链式约束)
其中Weighted Chain链的默认是在可用空间中平均分配元素。如果一个或多个元素使用MATCH_CONSTRAINT,它们将使用可用的空白空间(在它们之间平分)。属性layout_constraintHorizontal_weight和layout_constraintVertical_weight将控制如何使用MATCH_CONSTRAINT在元素之间分配空间。例如,在使用MATCH_CONSTRAINT的包含两个元素的链上,第一个元素使用权重为2,第二个元素的权重为1,第一个元素占用的空间将是第二个元素的两倍。例如要把A、B、C按钮水平排成一行,可以用链式约束
这样ButtonA、B、C就在水平方向形成了一条Chain,并且底部对齐。此时ButtonA新增app:layout_constraintHorizontal_chainStyle的属性设置,这个属性在一条Chain中只会出现在第一个控件中,这个控件是整条Chain的Head
6.1、创建约束链Chain 链
Chain 链是由多个 Views 组合的,所以要创建一个 Chain 链就需要先选择多个想要链接到一起的 Views ,然后再右键选择 ‘Center Horizontally’ 或者 ‘Center Vertically’ 来创建水平链或者垂直链。
如下图创建一个水平方向的约束链:
由图可知 Chain 链两边末端的两个 View 已经存在了相对于父组件的左边缘和右边缘的约束。 Chain 链的创建定义的是 Chain 链组件之间的间隙关系,并不影响原有的非成员间的约束
Chain 链组件之间的连接类似于链条图案,而边缘两端的 View 与 父组件之间的连接类似于弹窗图案。最外面的连接图案代表了 Chain 链的链接模式(chain mode),链接模式决定了 Chain 链如何分配组件之间的剩余空间,你可以从 Chain 链每个组件下面的 “转换 Chain 模式” 按钮来切换 Chain 链模式。
6.2、 spread 模式(Chain 链的默认模式)——它将平分间隙让多个 Views 布局到剩余空间
6.3、Spread Inside Chain 链模式——它将会把两边最边缘的两个 View 到外向父组件边缘的距离去除,然后让剩余的 Views 在剩余的空间内平分间隙布局
6.4、Spread 系的权重
spread 和 spread inside Chain 链可以设置每个组件的 weight 权重,这跟 LinearLayout 的 weight 权重设置很像。当前版本(Android Studio 2.4 alpha 7)的视图编辑器不能直接操作设置这个权重,不过我们可以通过属性视图(properties 视图)来手动设置属性。
对特定的组件设置 spread 权重,首先得选择这个 View 组件,假设该 View 是在一个水平的 Chain 链中,那么需要在属性视图(properties 视图)中设置 android:layout_width="0dp" 然后修改 app:layout_constraintHorizontal_weight="1"
View 组件在 blueprint 蓝图视图模式中的改变,它的上边和下边缘都从直线变成了类似手风琴的线条,这符号就表示了 spread 或 spread inside Chain 链模式下的被设置了权重的组件。同时要注意的是,在 packed Chain 链模式下设置权重 weight 并没有作用。就是说并不像 spread 和 spread inside 模式中表现的占据尽可能的剩余空间,在 packed 模式下该组件就会被收缩成 0 大小。
XML 中设置 Chain 链,在 XML 中设置 Chain 链模式只需要设置好双向互补的约束即可成链,例如以下例子,在 textView 中设置了约束属性 app:layout_constraintEndToStartOf="@+id/textView2" ,而相对的在 textView2 也设置了约束属性 app:layout_constraintStart_toEndOf="@+id/textView" ,本质上就是创建两个约束条件,同一对锚点但是方向相反的约束条件,这就是 Chain 链的定义方式。
另外,textView 中的约束属性 app:layout_constraintHorizontal_chainStyle="spread" 就是指定了链模式 spread 你可以通过修改成 spread inside 或 packed 来切换链模式,而且这个约束属性必须在链头,即是链组件中的第一个组件。而设置链模式的 bias 可以通过设置约束属性 app:layout_constraintHorizontal_bias="0.75" 从 0.0 - 1.0 。最后,我们就可以通过设置属性 android:layout_width="0dp" 以及 app:layout_constraintHorizontal_weight="1" 来设置 Chain 链中组件的权重。
<?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="match_parent"
tools:context="com.stylingandroid.scratch.MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@+id/textView2"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="TextView" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@+id/textView3"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="141dp"
tools:text="TextView" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView2"
app:layout_constraintTop_toTopOf="parent"
tools:text="TextView" />
</android.support.constraint.ConstraintLayout>
6.5、Packed Chain 链模式——它将所有 Views 打包到一起不分配多余的间隙(当然不包括通过 margin 设置多个 Views 之间的间隙),然后将整个组件组在可用的剩余位置居中
在 packed chain 链模式,打包在一起的 Views 组可以进一步通过控制修改 bias 值来控制打包组的位置,在例子中 bias 模式是 0.5 将 Views 组居中。
7、虚拟助手对象
在ConstraintLayout中有一类对象,在运行的时候不显示任何UI效果,只是作为参照物辅助布局,GuideLine就是其中之一。GuideLine分为水平引导线和垂直引导线。
并且支持设置在屏幕中所处的位置,可以使用layout_constraintGuide_begin和layout_constraintGuide_end设置具体dp值,也可以使用layout_constraintGuide_percent来设置比例。实际上Guideline 类其实就是一个 View,而且它不会渲染任何东西因为它实现了一个 final 的 onDraw() 而且固定了它的可见性为 View.GONE ,这就决定了运行时不会显示任何东西,而在 View 的 layout 布局过程中它会占据一个位置,而其他组件可以通过它来布局对齐。所以实际上的 Guideline 只是一个极其轻量级没有任何显示但是可以用于约束布局对齐的 View 组件,当我们以创建从 view 的一个锚点到参照线的约束 constraint 对象来根据参照线来对齐这个 view时,参照线移动时,受约束的 view 也会跟着参照线一起移动,最后,参照线 Guideline 拥有了一个属性 app:orientation="vertical" 来描述它是一个垂直的参照线(此处也可以设置为 horizontal)。它还有属性app:layout_constraintGuide_begin="16dp" 来描述它是一个对齐父组件的 start 边缘的 16dp 偏移量处。再次提醒的是,应该用 start 边缘而不是 left 边缘。当然切换向 end 类型的话,可以使用另一个属性 app:layout_constraintGuide_end="..." ,切换为百分比类型的参照线则是设置属性 app:layout_constraintGuide_percent="0.5" 值得取值范围为 0.0 到 1.0 ,描述的是百分比偏移量。
三、ConstraintLayout的图形界面操作
1、约束手柄类型
主要有三种手柄类型:调整手柄、约束手柄、基线手柄
边角上的小正方形是缩放的控制点,通过拖拉这些点就可以对 View进行缩放。但是这个大多数情况并不是很适用,因为使用这种方式进行缩放后的组件将保持固定的尺寸,而我们往往更需要 View根据具体情况响应式大小。每条边中间的锚点就是约束锚点,我们就是用这个锚点来创建约束的。其中而锚点里面有蓝点表示这个锚点已经存在了一个约束,反之,空心锚点则表示没有约束。
2、建立约束
首先选择一个约束手柄,并按住鼠标拖动到另外一个控件的手柄原点上,当链接线变成绿色的时候松开鼠标即可创建一个约束。
添加基线约束
居中约束
参考http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0520/4287.html和constraintlayout