ConstraintLayout 入门指南

0. 为什么要引入ConstraintLayout

你可能在之前的UI开发中遇到过以下问题:

  • RelativeLayout(以下简称RL)的性能开销较大,而你又很难不使用RL;
  • 想要按比例布局就要使用layout_weight属性,想要使用layout_weight属性就要使用LinearLayout(以下简称LL)或者TableLayout(以下简称TL),然后你在原先同级的每个布局外再嵌套一层布局以使用layout_weight;
  • 按固定宽高比布局等更高阶的布局需求,原先的各类布局方式都不能很好的支持,可能需要通过Java代码,在运行中二次实现;
  • 亦或者你只是想尝试下这款Andorid官方力推的新布局,看看它有什么新特性。

1. 准备工作

1.1 确保SDK Tools中已经下载了ConstraintLayout(以下简称CL)的支持库:

1.2 gradle中增加对ConstraintLayout的依赖:

compile 'com.android.support.constraint:constraint-layout:1.0.2'

1.3 在使用到ConstraintLayout的xml文件头部添加标签:

xmlns:app="http://schemas.android.com/apk/res-auto"

1.4 如果xml能正常联想出ConstraintLayout,并且其子View能正常联想出ConstraintLayout的相关属性,说明ConstraintLayout已经成功依赖:

2. Step by Step上手

先定一个小目标:将RL / LL实现的需求,通过CL来实现一遍。虽然Android Studio 2.3已经支持将其他布局自动转换成CL:

但还是建议先亲自上手码一遍:

  • 理解CL的布局规则;
  • 自动转换CL的功能目前还不是很完善,可能所见非所得。自动转换后还是要手动check下效果的。

2.1 相对布局

RL最常见的使用场景:我要控件B在控件A/父布局的上、下、左、右边,我要控件B跟控件A/父布局间距xxx dp。 e.g:控件B位于控件A右侧50dp:

  • RL实现
<RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView
          android:id="@+id/tv_a"
          android:layout_width="100dp"
          android:layout_height="100dp"
          android:background="@color/colorPrimary"/>

      <TextView
          android:id="@+id/tv_b"
          android:layout_width="100dp"
          android:layout_height="100dp"
          android:layout_toRightOf="@id/tv_a"
          android:layout_marginLeft="50dp"
          android:background="@color/colorAccent"/>
</RelativeLayout>
  • CL实现
<android.support.constraint.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView
          android:id="@+id/tv_a"
          android:layout_width="100dp"
          android:layout_height="100dp"
          android:background="@color/colorPrimary"/>

      <TextView
          android:id="@+id/tv_b"
          android:layout_width="100dp"
          android:layout_height="100dp"
          android:layout_marginLeft="50dp"
          app:layout_constraintLeft_toRightOf="@id/tv_a"
          android:background="@color/colorAccent"/>
</android.support.constraint.ConstraintLayout>

看上去几乎没有什么差别。事实上,RL中所有的属性CL中都有与之对应的项目,稍加熟悉即可完成转换:

CL属性

RL属性

layout_constraintLeft_toLeftOf

layout_alignLeft

layout_constraintLeft_toRightOf

layout_toRightOf

layout_constraintRight_toLeftOf

layout_toLeftOf

layout_constraintRight_toRightOf

layout_alignRight

layout_constraintTop_toTopOf

layout_alignTop

layout_constraintTop_toBottomOf

layout_below

layout_constraintBottom_toTopOf

layout_above

layout_constraintBottom_toBottomOf

layout_alignBottom

layout_constraintBaseline_toBaselineOf

layout_alignBaseline

layout_constraintStart_toEndOf

layout_toEndOf (API 17)

layout_constraintStart_toStartOf

layout_alignStart (API 17)

layout_constraintEnd_toStartOf

layout_toStartOf (API 17)

layout_constraintEnd_toEndOf

layout_alignEnd (API 17)

而相对于父布局的相对布局属性,CL的规则是:将父布局当做一个id=”parent”的对象来对待。也比较好理解:

CL属性

RL属性

layout_constraintTop_toTopOf=”parent”

layout_alignParentTop=”true”

layout_constraintBottom_toBottomOf=”parent”

layout_alignParentBottom=”true”

layout_constraintLeft_toLeftOf=”parent”

layout_alignParentLeft=”true”

layout_constraintRight_toRightOf=”parent”

layout_alignParentRight=”true”

layout_constraintStart_toStartOf=”parent”

layout_alignParentStart=”true”

layout_constraintEnd_toEndOf=”parent”

layout_alignParentEnd=”true”

layout_constraintLeft_toLeftOf=”parent”

layout_constraintRight_toRightOf=”parent”

layout_centerHorizontal=”true”

layout_constraintTop_toTopOf=”parent”

layout_constraintBottom_toBottomOf=”parent”

layout_centerVertical=”true”

layout_constraintLeft_toLeftOf=”parent”

layout_constraintRight_toRightOf=”parent”

layout_constraintTop_toTopOf=”parent”

layout_constraintBottom_toBottomOf=”parent”

layout_centerInParent=”true”

2.2 固定比例间距

layout_weight属性常见的使用场景:我要控件A的左间距和右间距的比例为x:(1-x)。 e.g:控件A左间距和右间距的比例是3:7:

  • TL实现(可能并非最优写法)
<TableLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      android:shrinkColumns="0,2">

      <TableRow>
          <View
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_weight="0.3"/>

          <TextView
              android:id="@+id/tv_a"
              android:layout_width="100dp"
              android:layout_height="100dp"
              android:background="@color/colorPrimary"/>

          <View
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_weight="0.7"/>
      </TableRow></TableLayout>
  • CL实现 <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_a" android:layout_width="100dp" android:layout_height="100dp" android:background="@color/colorPrimary" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintHorizontal_bias="0.3"/> </android.support.constraint.ConstraintLayout>

在设置了各类居中属性的基础上,通过layout_constraintHorizontal_bias和layout_constraintVertical_bias两个属性,可以简单直观的完成间距比例的设置。

2.3 固定比例宽高

原先,在未指定宽高具体数值的情况下,让View / ViewGroup按照比例动态调整宽高比,实现起来比较麻烦。你可能需要等到View / ViewGroup绘制出来后,拿到它的LayoutParams,获取固定边的长度,计算出被动边的长度,最后将LayoutParams set回去。而有了CL提供的layout_constraintDimensionRatio属性,一行xml即可搞定。

e.g:控件A按照宽高比4:3展示,宽为固定边,高为被动边:

<android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <!--layout_constraintDimensionRatio的H/W表示被动调整的是高(H)或是宽(W)-->
    <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginLeft="100dp"
        android:layout_marginRight="100dp"
        app:layout_constraintDimensionRatio="H,4:3"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>

上面的示例中,layout_width和layout_height都设置为了0dp,0dp在CL布局中等同于MATCH_CONSTRAINT——CL的一个新属性常量。在CL中,子View / ViewGroup无法使用match_parent属性。

MATCH_CONSTRAINT搭配不同的属性有不同的意义:

  • 设置layout_constraintDimensionRatio属性的情况下,代表该边长度由layout_constraintDimensionRatio动态调整;
  • weighted chain(后面会提到)中,代表该边长度由layout_constraintHorizontal_weight或layout_constraintVertical_weight动态调整;
  • 其他情况下,等同于match_parent,代表该边长度由各margin动态调整;

2.4 GoneMargin

layout_goneMarginStart layout_goneMarginEnd layout_goneMarginLeft layout_goneMarginTop layout_goneMarginRight layout_goneMarginBottom

layout_goneMargin系列是CL中新加入的属性。相对布局的两个控件,其中一方Visibility == Gone时,另外一方将会根据layout_goneMargin系列属性的值重新规划自己的间距。比较常用于在相对布局中保持各个控件的位置。

e.g:控件B在控件A设置Visibility == Gone后保持在原来的位置:

<android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp">

        <TextView
            android:id="@+id/tv_a"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="A"
            android:textSize="30sp"/>

        <TextView
            android:id="@+id/tv_b"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginLeft="50dp"
            app:layout_goneMarginLeft="100dp"
            app:layout_constraintLeft_toRightOf="@id/tv_a"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:text="B"
            android:textSize="30sp"/>
</android.support.constraint.ConstraintLayout>

2.5 Chain

chain是CL中新加入的控件与控件间的关系。组成chain的多个控件,可以在同一方向上更加方便的完成复杂的布局要求。

2.5.1 组成chain

多个控件组成chain需要满足以下条件:

  1. 控件间的布局存在相互依赖关系(你依赖我布局,我也依赖你布局);
  2. 两个以上的控件,相互依赖关系需要保持在同一个方向上(都是水平方向上的依赖:Left_toRightOf / Right_toLeftOf;或者都是垂直方向上的依赖:Top_toBottomOf / Bottom_toTopOf);

听起来很绕。e.g:控件A、B、C在水平方向上组成chain:

<android.support.constraint.ConstraintLayout
     android:layout_width="match_parent"
     android:layout_height="wrap_content">

     <TextView
         android:id="@+id/tv_a"
         android:layout_width="50dp"
         android:layout_height="50dp"
         android:layout_marginRight="50dp"
         app:layout_constraintRight_toLeftOf="@+id/tv_b"/>

     <TextView
         android:id="@+id/tv_b"
         android:layout_width="50dp"
         android:layout_height="50dp"
         app:layout_constraintLeft_toRightOf="@id/tv_a"
         app:layout_constraintRight_toLeftOf="@+id/tv_c"/>

     <TextView
         android:id="@+id/tv_c"
         android:layout_width="50dp"
         android:layout_height="50dp"
         android:layout_marginLeft="50dp"
         app:layout_constraintLeft_toRightOf="@id/tv_b"/>
</android.support.constraint.ConstraintLayout>

示例中,控件A和控件B通过 layout_constraintRight_toLeftOf=”@+id/tv_b”layout_constraintLeft_toRightOf=”@id/tv_a” 达成了水平方向的相互依赖关系。控件B与控件C同理。同时,又由于控件A与B、控件B与C均是水平方向的chain关系,控件A、B、C三者在水平方向组成了一条chain。

在xml的Design窗口下,组成chain的控件间会出现一条链条:

2.5.2 设置chain style

水平方向chain最左边的控件和垂直方向chain最顶部的控件被成为head chain。通过对head chain添加chainStyle属性,可以设置该条chain在水平或垂直方向上的chainStyle:

  • layout_constraintHorizontal_chainStyle
  • layout_constraintVertical_chainStyle

chainStyle属性一共有三种:spread、spread_inside、packed。再配合其他属性,最终可以组成五种chain style:

chain style

设置方式

Spread Chain

chainStyle = “spread”

Spread Inside Chain

chainStyle = “spread_inside”

Packed Chain

chainStyle = “packed”

Packed Chain with Bias

chainStyle = “packed”layout_constraintHorizontal_bias layout_constraintVertical_bias

Weighted Chain

chainStyle = “spread” layout_constraintHorizontal_weight layout_constraintVertical_weight layout_width = “0dp” layout_height = “0dp”

官方的这张图已经比较清楚的展示了chain style各自的布局效果:

前四种chain style的设置和效果都比较简单,不再赘述。 重点介绍下Weighted Chain,Weighted Chain的设置方式相对比较复杂,以水平方向的chain为例:

<android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_a"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:background="@color/colorPrimary"
            app:layout_constraintHorizontal_chainStyle="spread"
            app:layout_constraintHorizontal_weight="0.33"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/tv_b"/>

        <TextView
            android:id="@+id/tv_b"
            android:layout_width="0dp"
            android:layout_height="50dp"
            app:layout_constraintHorizontal_weight="0.33"
            app:layout_constraintLeft_toRightOf="@id/tv_a"
            app:layout_constraintRight_toLeftOf="@+id/tv_c"/>

        <TextView
            android:id="@+id/tv_c"
            android:layout_width="0dp"
            android:layout_height="50dp"
            app:layout_constraintHorizontal_weight="0.33"
            app:layout_constraintLeft_toRightOf="@id/tv_b"
            app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
  • chain head设置chainStyle为spread;
  • chain中控件设置了layout_constraintHorizontal_weight参数;
  • chain中控件都需要将layout_weight设置为0dp(2.3中提到的MATCH_CONSTRAINT);

除此以外,Weighted Chain还有以下特征:

  • Weighted Chain中的控件也允许在chain方向上使用wrap_content自适应控件宽 / 高,且布局时优先满足设置为wrap_content的控件; e.g:将示例中的控件C layout_width设置为wrap_content:
  • Weighted Chain中的控件既不设置constraint_weight,也不在chain方向上将边设置为wrap_content,那么该控件将被隐藏;
  • 如果Weighted Chain中的控件在chain方向上设置了margin,margin的距离将计算入该控件实际占有的布局范围; e.g:将示例中的控件B左右各添加10dp margin后,控件A和C的实际占有布局并没有被压缩:

3. 简单的性能测试

官方称,CL相较于RL,在onMeasure() / onLayout()上的性能开销提升了40%:

为此,笔者也做了一个简单的性能试验来验证: 分别用CL和RL构造了一个3 × 2的相对布局矩阵,布局矩阵中的控件均使用wrap_content自适应大小,并设置有margin,使用ListView不断的绘制:

同时,使用API 24新加入的OnFrameMetricsAvailableListener回调,监听Window在渲染时,在onMeasure() / onLayout()上实际花费的时间。最终数据如下:

抛开绝对数值,CL相对RL,在onMeasure() / onLayout()上大致减少了10%的时间,并没有官方宣传的那么明显。可能是测试的布局嵌套并不是很深,亦或者布局中的控件并不是很多。

4. 个人开发体验

使用CL开发也有一段时间了,个人觉得CL与RL、LL、TL这些老前辈相比,在按比例布局、线性布局上面的支持更加完善,相关开发痛点可以用较少的xml描述完成了。简单的相对布局上,抛开微小的性能优势,CL和RL几乎没有什么差距,两者可以无缝转换。较复杂的相对布局上,CL相较RL代码不够直观,写出来的xml可读性比较差,chain + constraint相对布局属性的组合想要实现与嵌套RL相同的效果,往往需要更多的xml代码。许多人认为CL的出现就是为了替换RL,个人觉得倒是更适合替换TL,替换RL可能需要功能更加强大的chain才能解决。 但不管怎么说,越来越多的使用CL是趋势。

不足之处,请多指教。

参考文献:

https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html

https://developer.android.com/reference/android/support/constraint/ConstraintLayout.html

http://blog.csdn.net/zxt0601/article/details/72683379

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端说吧

css布局 - 垂直居中布局的一百种实现方式(更新中...)

1. line-height行高简单粗暴实现法:line-height:Npx(N = 与元素高度相同的值)

771
来自专栏HTML5学堂

CSS3实现loading图

HTML5学堂:学习CSS3,重要的并不是那几个命令,而是如何应用CSS3的知识实现网站中的效果,换句话说,“只有想不到,没有做不到”。在这个效果当中,主要用到...

3524
来自专栏菜鸟前端工程师

html+css学习笔记012-表格

老板说:他们都没什么经验,做不出来的你就做出来给他们看看,让他们知道你的能力有多强大,他们就服你了

483
来自专栏流星博客

CSS的奇淫技巧

当一个盒容器的两条边框在边角处相交时,浏览器就会在交点处按某个角度绘制接合线。 如果将这个盒容器的width和height设置为0,并为每条边框设置一个较粗的w...

54712
来自专栏Coco的专栏

【前端攻略】最全面的水平垂直居中方案与flexbox布局

1603
来自专栏web前端教室

药不能停之--CSS怎么学怎么记效果好?

image.png CSS这个东西,唉,我也是不知道什么时候就学会了的。想当年根本没有前端,做网页就是table套table的时代,CSS基本上只能一种用法...

1866
来自专栏熊二哥

Html与CSS快速入门01-基础概念

Web前端技术一直是自己的薄弱环节,经常为了调节一个简单的样式花费大量的时间。最近趁着在做前端部分的开发,果断把这部分知识成体系的恶补一下。内容相对都比较简单,...

1907
来自专栏AndroidTv

一起撸个简单粗暴的Tv应用主界面的网格布局控件(下)

上一篇中我们已经一起学了怎么简单粗暴的撸个支持动态布局的网格控件出来,但在上一篇的介绍中,并没有学习实现网格控件的滑动效果,所以本篇就来讲讲,要如何让我们的网格...

3278
来自专栏coding for love

CSS常用布局实现03-水平垂直居中

其实,水平居中和垂直居中都是水平垂直居中的一部分,所以这一章节所讲到的方法稍微改下就可用于前面两章。说道水平垂直居中,那么居中的元素肯定是有宽度和高度的,要么是...

711
来自专栏Material Design组件

Material Design — App bars: bottomApp bars: bottom

2178

扫码关注云+社区