因文章发布年代久远,下面放出更好更新教程的地址:
ConstraintLayout 约束布局,是16年 google 新出的,经过一年的发展,在17年中旬大量曝光,好多大神的微信订阅号和 google 官方都在推荐使用,为啥,真的好用呗。
简单来说下ConstraintLayout ,其实他是 RelativeLayout 的进化版,把线性布局的一些特点融合进了相对布局中,比如weight,提供了更多的控件排列方式,性能上也有优势,RelativeLayout 天然的会执行2遍 measu,ConstraintLayout 最大的好处也是我们最看中的就是可以减少布局层级,提高页面绘制效率,官方测试的 demo 控件很少还可以提高40%的性能呢。
ConstraintLayout 的特点
有必要在想大家介绍如何使用 ConstraintLayout 之前,让大家了解下 ConstraintLayout 的新东西都有啥:
- 支持控件的 W/H 按照比例显示,比如图片按4:3显示
- 在相对布局中支持 weight 比重
- 优化 view 在 gone 后不会丢位置,以防显示错乱
- chain 链,支持指定的 view 组成横向/纵向的显示排列
- 支持view 按照辅助线 guideView 定位显示
- 支持 view 上下边距按照比例显示
来看下 ConstraintLayout 的属性及和 RelativeLayout 的对比
ConstraintLayout 的属性如下:
和 RelativeLayout 的属性对比
既然都说了ConstraintLayout 是 RelativeLayout 的进化版,那么基础属性使用那肯定和之前的一样,就是名字改了下,注意一下规律即可完成转换:
- 使用 parent 代替父布局
- app:layout_constraintLeft_toLeftOf="parent" / "@+id/view_text13"
to左右边两边一样,参数填入parent表示在父布局的那边,也就是和父布局的那边对齐,参数填入具体的 view id 表示和目标的 view 的那边对齐,比如这个就表示我的左侧和 view 的左侧对齐 - app:layout_constraintLeft_toRightOf="@+id/view_text11"
to左右边两边不一样,看右边的是哪个方便,就表示我在 view 的那边,比如这个就表示我在 view 的右边 - 注意横向居中,和纵向居中和以前写法和以前完全不同,看上图属性对照,其实很好理解的,不细说了
- match_parent 这个参数无效了,虽然还能使用,但是没用了,0dp 表示match_parent,但是也表示 W/H 由具体的约束条件来计算,这个具体要结合下面的讲解来看,这里要特别注意,0dp 有时也是还是表示原始0dp 的意思的,要注意和约束配合使用
好了,看完属性的转换,基本我们就可以使用 ConstraintLayout 了,下面我们正式开始
添加依赖
ConstraintLayout 是以依赖包的方式添加进来的
compile 'com.android.support.constraint:constraint-layout:1.0.2'
先写一个简单的 ConstraintLayout 示例
上图这个简单的布局用 ConstraintLayout 怎么写,还是得先再熟悉熟悉从 RelativeLayout 到 ConstraintLayout 的转换
<View
android:id="@+id/view_x01"
android:layout_width="200dp"
android:layout_height="0dp"
android:layout_marginTop="50dp"
android:background="@color/colorAccent"
app:layout_constraintDimensionRatio="H,4:3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"></View>
<TextView
android:id="@+id/view_x02"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="!我是测试啊测试!!我是测试啊测试!!我是测试啊测试!!我是测试啊测试!!我是测试啊测试!!"
app:layout_constraintLeft_toRightOf="@+id/view_x01"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/view_x01"/>
<TextView
android:id="@+id/view_x03"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="我是大圣!!"
app:layout_constraintBottom_toBottomOf="@+id/view_x01"
app:layout_constraintLeft_toRightOf="@+id/view_x01"
app:layout_constraintRight_toRightOf="parent"/>
还是很简单的,大家回味一下,下面开始 ConstraintLayout 的新特征了
按宽高比例显示
举个例子,我想让一个图片按照固定的例子显示,比如 4:3的比例,先看代码
<ImageView
android:id="@+id/view_top"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="@drawable/x01"
app:layout_constraintDimensionRatio="H,4:3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
效果:
这个设置有点绕,需要 W/H 设置和约束条件密切配合,缺一个都出不来效果:
- 约束属性:
app:layout_constraintDimensionRatio="H,4:3",就是这个啦,打击一看也能发现他不是,我来说下这个设置的意思啊,有点不好理解,你去别的帖子找绝对没我说的到点上: - 首先注意这个 "H,4:3" ,这个view 宽高比是 4:3,以 W 为基准动态设置 H 高这个属性,就是 "H,4:3" 这个里面,我们写 W 和 H 谁,就表示以另外一个边为基准,动态计算修改我们写的这个边的值,我们写的这个边一定要设置成0dp 表示由约束条件来计算具体值才行,写 warpcontent 不行,不信的可以试试
- 我们需要注意这个基准边,基准边必须是一个具体值,要不基准边取不到一个具体( 如100dp),后面怎么计算
- 这个基准边我们要是填满父布局的宽怎么办,match_parent 不能用了,写0dp 的话打击可以自己试试显示不出来,这时写0dp 就表示没有宽高的意思了,但是 ConstraintLayout 用0dp 代替了match_parent 了,那怎么办啊,经过调试我们加上
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
就行了,为啥啊,可以这样解释:在有约束条件时 0dp 表示控件的具体宽高由约束条件计算而来,这里我们控件设置成横向居中就是代替了 match_parent 了,在 0dp 不能表示 match_parent 时,我们都可以这样些,主力 千万要注意,纵向也是同理的
- 结合上面的代码,我们图片想占满屏幕的款,然后按4:3的比例来显示。可以看到imageview的宽高都设置成0dp,然后添加了横向居中条件,大家可以自己试试,少一个看看会是什么样的。
chain 链
chain 链这是新加入的东西,其实也是很好理解,就是让一组 view l定位,形成横向/纵向的的排列,其实就是类似于线性布局的方向。
解释下两两相互依赖:A 在 B 的左面,B 在A的右边,这就是组成了 chain ,chain 可以多个 view 相互组合依赖,数量不限
需要注意的是,view 组成 chain 链之后,首位置的 view 的 magin,padding 会失效,这时我们可以使用 layout_editor_absoluteY / layout_editor_absoluteX 来替代 magin。
<TextView
android:id="@+id/view_text11"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name11"
app:layout_constraintBottom_toTopOf="@+id/view_text21"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/view_text12"
app:layout_constraintVertical_chainStyle="packed"/>
<TextView
android:id="@+id/view_text12"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name12"
app:layout_constraintLeft_toRightOf="@+id/view_text11"
app:layout_constraintRight_toLeftOf="@+id/view_text13"
app:layout_constraintTop_toTopOf="@+id/view_text11"/>
<TextView
android:id="@+id/view_text13"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name13"
app:layout_constraintLeft_toRightOf="@+id/view_text12"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/view_text12"/>
<TextView
android:id="@+id/view_text21"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name21"
app:layout_constraintBottom_toTopOf="@+id/view_text31"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/view_text22"
app:layout_constraintTop_toBottomOf="@+id/view_text11"/>
<TextView
android:id="@+id/view_text22"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name22"
app:layout_constraintLeft_toRightOf="@+id/view_text21"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/view_text21"
/>
<TextView
android:id="@+id/view_text31"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name31"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view_text21"/>
<TextView
android:id="@+id/view_text32"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name32"
app:layout_constraintLeft_toRightOf="@+id/view_text31"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/view_text31"
/>
效果图:
可以看到有3行,纵向方向是组成一个 chain,第一行横向也是一个 chain。每一个 chain 的第一个 view 有个参数可以设置整个 chain 的样式, app:layout_constraintHorizontal_chainStyle="packed" 就是这个参数,这个参数就是设置这组 chain 的排列样式,我觉得没啥实际意义,需求场景很少碰到这种样式,其他的样式效果如下:
官网有个图:
weight 比重
weight 就是线性布局的那一套,用法也一样,比如一组横向的 view 直接用 weight 和组成 chain 再使用 weight 效果都一样,代码简单一看就会
<TextView
android:id="@+id/view_text11"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name11"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/view_text12"/>
<TextView
android:id="@+id/view_text12"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name12"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@+id/view_text11"
app:layout_constraintRight_toLeftOf="@+id/view_text13"
app:layout_constraintTop_toTopOf="@+id/view_text11"/>
<TextView
android:id="@+id/view_text13"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name13"
app:layout_constraintLeft_toRightOf="@+id/view_text12"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/view_text12"/>
效果图:
Guideline 辅助线
辅助线就是用来定位的,帮助 view 定位显示的,有横线和竖线,可以部分用来做屏幕适配,看需求,属性如下:
- layout_constraintGuide_begin
- layout_constraintGuide_end
- layout_constraintGuide_percent
可以通过上面3个属性其中之一来确定属性值位置。
begin=30dp,即可认为距离顶部30dp的地方有个辅助线,根据orientation来决定是横向还是纵向。
end=30dp,即为距离底部。 percent=0.8即为距离顶部80%。
比如我要让 view 左上角位于屏幕的中间处显示
<android.support.constraint.Guideline
android:id="@+id/guide_h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"
/>
<android.support.constraint.Guideline
android:id="@+id/guide_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5"
/>
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/x01"
app:layout_constraintLeft_toRightOf="@+id/guide_v"
app:layout_constraintTop_toBottomOf="@+id/guide_h"
/>
先声明2个辅助线,然后就可以在辅助线的上下左右做显示,用法简单,一看就会,注意是作用于 view 的四周边缘,而不是中心点啊。
bia 边距
这个也是一个比例,不过不是 view 的 W/H 了,而是view上下/左右边距的比例,参数如下
app:layout_constraintHorizontal_bias="0.8"
这表示做边距占空余控件的80%,看图就好理解了:
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/x01"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.8"
/>
这是一个比例,比较适合FloatingActionButton
Guideline 和 bias 的区别
刚看这2个有些混,记住 Guideline 是绝对位置,bias 是相对位置,集合一张对比图来看,Guideline 和 bias 都设置0.9
看到对比就好理解了,图右下角出去的那个就是 Guideline 辅助线,能显示全的那个是 bias
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/x01"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.9"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9"
/>
<android.support.constraint.Guideline
android:id="@+id/guide_h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.9"
/>
<android.support.constraint.Guideline
android:id="@+id/guide_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.9"
/>
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/x01"
app:layout_constraintLeft_toRightOf="@+id/guide_v"
app:layout_constraintTop_toBottomOf="@+id/guide_h"
/>
view gone 的优化
在相对布局时,如果用于定位锚点的 view 我们 gone 不显示了,那么参照这个锚点的 view 都在在左上角显示,造成显示错乱,这次在ConstraintLayout中,google 应该是优化了这一点,经过测试,锚点 view 在 gone 后,实际上是宽高都会被置为0,magin 和 paddi哪个会无效,但是位置还在,依赖于这个锚点的 view 的显示不会想相对布局一样错乱,还专门有一组 gone 打头的参数,在 锚点view gone 之后,依赖于锚点 view 的其他 view 以 goneMagin 作为新的 magin 用于重新定位,常用与横向的一组 view,比如设置页面,每行的右边根据状态不同显示不同的按钮
还是用说 chain 时使用的那3行 view,我们让中间那行的第一个 gone,看看效果
看到没,第三行显示没有错位,第二行右边的 view 实际上还是显示在第二行,只不过占全屏的宽度,并被第三行挡住了,而我们 gone 的 view 呢,看我画红圈的位置,实际不是不显示了,是宽高为置为0从而造成的不显示,这样比相对布局真是好的太多了,相对布局因为应付这种情况,我是会使用好几层用来占位的无用的 layout 的
<TextView
android:id="@+id/view_text11"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name11"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/view_text12"/>
<TextView
android:id="@+id/view_text12"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name12"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@+id/view_text11"
app:layout_constraintRight_toLeftOf="@+id/view_text13"
app:layout_constraintTop_toTopOf="@+id/view_text11"/>
<TextView
android:id="@+id/view_text13"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name13"
app:layout_constraintLeft_toRightOf="@+id/view_text12"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/view_text12"/>
<TextView
android:visibility="gone"
android:id="@+id/view_text21"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name21"
app:layout_constraintBottom_toTopOf="@+id/view_text31"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/view_text22"
app:layout_constraintTop_toBottomOf="@+id/view_text11"/>
<TextView
android:id="@+id/view_text22"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name22"
app:layout_constraintLeft_toRightOf="@+id/view_text21"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/view_text21"
/>
<TextView
android:id="@+id/view_text31"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="name31"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view_text21"/>
<TextView
android:id="@+id/view_text32"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="name32"
app:layout_constraintLeft_toRightOf="@+id/view_text31"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/view_text31"
/>