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"
    />

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容