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 条评论
登录 后参与评论

相关文章

来自专栏Android知识点总结

Android控件之ImageView

张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com

580
来自专栏帘卷西风的专栏

编写简易斜45度地图编辑器

      最近在研究cocos2dx的地图,最开始使用的是Tiled,这个编辑器做比较小的地图还是比较强大的,不过做大地图的时候,有一些功能不太方便并且有缺陷...

923
来自专栏码生

iOS OC swift 自定义 popover 泡泡

git 地址:https://github.com/TieShanWang/KKPopover

757
来自专栏编码前线

ConstraintLayout(约束布局)的使用

这些属性会引用另一个控件的id或者parent(这会引用父容器,即ConstraintLayout)

1003
来自专栏HTML5学堂

一步步教你弹性框架-上篇

HTML5学堂:本系列主要在于跟大家分享弹性运动框架的制作方式。弹性运动框架的运动方式类似于弹簧,有一种回弹的效果,在网站中的一些特效当中还是有一些应用的。实现...

3458
来自专栏葬爱家族

Android高德之旅(3)UI Setting

前两篇讲到了地图的基础显示和地图类型,今天来记录下高德地图交互相关的设置。地图的触摸事件很丰富,有单击、双击、单指拖拽、双指拖拽、双指旋转、双指缩放等,高德提供...

1223
来自专栏Android 开发学习

ConstraintLayout 使用简介一 背景二 demo三 进一步升级打怪四 更多

4474
来自专栏Android知识点总结

5-VI--ListView事件全解析

952
来自专栏Android知识点总结

VV-安卓布局总汇篇

layout_constraintHorizontal_bias layout_constraintVertical_bias

914
来自专栏非典型技术宅

iOS动画系列之五:基础动画之缩放篇&旋转篇Swift+OC1. 思路和最终成果2. 抽取公共方法3. 懒加载Layer4. 添加动画

991

扫码关注云+社区