android ui 学习系列 - ConstraintLayout 约束布局

3-161212104A2.jpg

因文章发布年代久远,下面放出更好更新教程的地址:


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 的属性对比


Snip20170924_11.png

Snip20170924_12.png

既然都说了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 示例

Snip20170924_13.png

上图这个简单的布局用 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"/>

效果:


Snip20170924_14.png

这个设置有点绕,需要 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"
    />

效果图:


Snip20170924_15.png

可以看到有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"/>

效果图:


Snip20170924_16.png

Guideline 辅助线

辅助线就是用来定位的,帮助 view 定位显示的,有横线和竖线,可以部分用来做屏幕适配,看需求,属性如下:

  • layout_constraintGuide_begin
  • layout_constraintGuide_end
  • layout_constraintGuide_percent

可以通过上面3个属性其中之一来确定属性值位置。

begin=30dp,即可认为距离顶部30dp的地方有个辅助线,根据orientation来决定是横向还是纵向。
end=30dp,即为距离底部。 percent=0.8即为距离顶部80%。

比如我要让 view 左上角位于屏幕的中间处显示

Snip20170924_17.png
 <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%,看图就好理解了:

Snip20170924_21.png
  <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

Snip20170924_22.png

看到对比就好理解了,图右下角出去的那个就是 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,比如设置页面,每行的右边根据状态不同显示不同的按钮

Snip20170924_23.png

还是用说 chain 时使用的那3行 view,我们让中间那行的第一个 gone,看看效果

Snip20170924_24.png

看到没,第三行显示没有错位,第二行右边的 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"
    />

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容