ConstraintLayout 介绍

不知道从什么时候开始,Android 死丢丢已经默认使用约束布局 ConstraintLayout 作为默认布局了,但是懒癌发作一直不想学习,每次都换成 LinearLayout,这次也忘记了为啥开始学习这个东西,学完发现还挺爽……写个笔记记录一下,哈哈

正文开始~

相对布局

属性集合

类似 RelativeLayout ,使用相对位置的属性来互相约束位置。具体的属性以及使用方式也类似 RelativeLayout,默认像 FrameLayout 一样堆叠在一起,使用属性讲层级关系区分开:

layout_constraintLeft_toLeftOf   当前控件的左侧与某个控件的左侧对齐
layout_constraintLeft_toRightOf  当前控件的左侧与某个控件的右侧对齐
layout_constraintRight_toLeftOf  当前控件的右侧与某个控件的左侧对齐
layout_constraintRight_toRightOf 当前控件的右侧与某个控件的右侧对齐

layout_constraintStart_toEndOf   同上
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf

layout_constraintTop_toTopOf       当前控件与某个控件顶端对齐
layout_constraintTop_toBottomOf    即当前控件某个控件的下面
layout_constraintBottom_toTopOf    即当前控件在某个控件的上面
layout_constraintBottom_toBottomOf 当前控件与某个控件底部对齐

layout_constraintBaseline_toBaselineOf 文本基线对齐
具体示例
<!-- 居中对齐实现方式 -->

<!-- 上下左右全部受 parent 约束,最后的效果就是「居中对齐」 -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="居中对齐"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

<!-- 同理,左右受 parent 约束,效果就是「水平居中对齐」-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="水平居中对齐"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

<!-- 同理,上下受 parent 约束,效果就是「垂直居中对齐」-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="垂直居中对齐"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"/>

居中对齐很好理解,下边我们来写一个正常的 item 布局看看:

ConstraintLayout 实现的 item 布局
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">

    <ImageView
        android:id="@+id/iv_logo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_launcher" />

    <!-- 设置标题名称 View 的左侧边缘位于 logo 的右侧 -->
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="虾吃虾涮(华贸店)"
        app:layout_constraintLeft_toRightOf="@+id/iv_logo"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- 设置价格 View 的底部靠近父布局,且顶部参考 titleView,同时左侧与 titleView 对齐 -->
    <TextView
        android:id="@+id/tv_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="¥64/人"
        android:textSize="13sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="@id/tv_title"
        app:layout_constraintTop_toBottomOf="@id/tv_title" />

    <!-- 设置 distanceView 紧贴屏幕右侧,且顶部与 priceView 对齐-->
    <TextView
        android:id="@+id/tv_distance"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1.1km"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/tv_price" />

    <!-- 设置 areaView 顶部与左侧都参考 priceView,底部位置参考 ivLogo-->
    <TextView
        android:id="@+id/tv_area"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="朝阳区 大望路"
        android:textSize="13sp"
        app:layout_constraintBottom_toBottomOf="@id/iv_logo"
        app:layout_constraintLeft_toLeftOf="@id/tv_price"
        app:layout_constraintTop_toBottomOf="@id/tv_price" />

    <!-- 设置 hotView 紧贴屏幕右侧,且顶部与 areaView 顶部对齐-->
    <TextView
        android:id="@+id/tv_curr_hot"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="当前人气89"
        android:textSize="13sp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/tv_area" />

    <!-- dividerView 位于整个布局的最底部,且始终位于 ivLogo 底部,并保持一定距离 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_marginTop="7dp"
        android:background="@android:color/darker_gray"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_logo" />

</android.support.constraint.ConstraintLayout>

可以发现使用约束布局实现比普通的 RL 实现还要简单,界面完全实现扁平化,没有任何嵌套。如果使用 LL 或者 RL 来实现同样的效果,代码要复杂多少想必不用我多说。

Bias 偏向

以上的内容就是基本使用了,把上下左右各种参考、依赖关系搞明白,本身没有多么复杂,使用起来也和 RL 差不多,下面来介绍一些新花样。

bias 很好理解,正如其英文本意一样,它表达的是偏移。当某一布局同时受两个相反方向的约束力时,该布局就会处于约束它的那两个力量的正中央。而 layout_constraintHorizontal_biaslayout_constraintVertical_bias 就是用在这种时候,用来将某一方向的约束力减弱。来自两侧的约束力可以为 parent,也可以是普通 View。

文字描述可能有点抽象,具体布局文件还是更好理解一些:

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

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is a text"
        app:layout_constraintLeft_toLeftOf="parent" />

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is a text"
        app:layout_constraintHorizontal_bias="0.3"
        app:layout_constraintLeft_toRightOf="@id/tv1"
        app:layout_constraintRight_toLeftOf="@id/tv3" />

    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is a text"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

布局很简单,三个 TextView 并排显示,左右两个分别紧贴父布局,中间一个受左右两侧布局约束,本来应该是位于两个 TextView 正中央,但是由于设置了 layout_constraintHorizontal_bias 小于 0.5,所以最后效果中间的 TextView 整体偏向左侧,展示如下图:

image

Circle 布局

这个看上去很厉害的!可以令 B 布局以 A 布局为圆心,然后用角度和半径距离来约束两个布局的位置。废话不多说,上图:

image

这个也很好理解,主要有三个属性:

layout_constraintCircle :      当前布局以哪个布局为圆心
layout_constraintCircleRadius :半径
layout_constraintCircleAngle : 摆放角度

我反正是给谷歌跪了……

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv_center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="Circle Center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <!-- 「测试布局」以 tv_center 为圆心,位于其 135° 方向的 100dp 处 -->
    <TextView
        android:text="测试布局"
        app:layout_constraintCircle="@id/tv_center"
        app:layout_constraintCircleAngle="135"
        app:layout_constraintCircleRadius="100dp"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

替代 MATCH_PARENT 的 MATCH_CONSTRAINT

在约束布局中,由于布局受各方约束控制,也就没有所谓的「match_parent」了。随之而来的需求则是,左边有个布局约束我,右边还有个布局约束我,然后我就想充满剩余的全部位置,「match_constraint」也就应运而生了。

说起来复杂,其实只需要把对应的 View 宽高设置为 0dp 即可,该 View 就会占据上剩余的所有可用空间。在这种情况下,谷歌给我们提供了几个额外的属性:

layout_constraintWidth_min   宽度最小值
layout_constraintHeight_min  高度最小值

layout_constraintWidth_max   宽度最大值
layout_constraintHeight_max  高度最大值

layout_constraintWidth_percent   宽度占剩余位置的百分比
layout_constraintHeight_percent  高度占剩余位置的百分比

具体示例如下:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Test Button"
        app:layout_constraintLeft_toLeftOf="parent" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintRight_toRightOf="parent" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constrainedWidth="true"
        android:text="Button"
        app:layout_constraintWidth_min="wrap"
        app:layout_constraintWidth_max="wrap"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        app:layout_constraintRight_toLeftOf="@id/btn2" />

</android.support.constraint.ConstraintLayout>
image

Chains 链

如果几个不同的 View 两两发生关联,如下图,则这几个 View 构成了一个 Chains(链)。

image

具体布局代码如下:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/btn2" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        app:layout_constraintRight_toLeftOf="@id/btn3" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toRightOf="@id/btn2"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

这样这三个 Button 就形成了一个横向的 Chain,在这个链的最左侧的元素成为链头,我们可以在其身上设置一些属性,来决定这个链的展示效果:

该属性为:

layout_constraintHorizontal_chainStyle
layout_constraintVertical_chainStyle

其取值可以为:spread、spread_inside、packed。

具体样式展示如下:

  1. spread,基本上就是按照权重等分
image
  1. spread_inside,也是等分展示,但是两侧吸附

    image
  2. packed,整条链挤在一起,居中展示

    image

官网有一个图来展示不同样式的 Chains,可以参考一下,也很形象:

image

虚拟辅助视图

与以往的 ViewGroup 不同,ConstraintLayout 还提供了几种辅助页面绘制的布局,这种布局一般表现为引导线之类,不会在页面上绘制,但是可以通过占位的方式,成为不同布局的约束条件。

GuideLine

顾名思义,GuideLine 可以创建基于父布局 ConstraintLayout 的水平或者垂直准线,从而帮助开发者进行布局定位。

这个布局有四个基本属性,依次为:

orientation 如上所述,用来表示是垂直方向还是竖直方向
layout_constraintGuide_begin 距离父亲的起始位置
layout_constraintGuide_end 距离父亲的结束位置
layout_constraintGuide_percent 距离父亲的位置,用百分比表示

经过试验,percent 优先级最高,其次是 begin,最后是 end,一般来讲使用 percent 就足够了

xml 以及对应的页面效果如下:

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

    <!-- 假设说现在需要将 ImageView 摆放到右下角的位置,就可以使用 GL 辅助实现-->
    <android.support.constraint.Guideline
        android:id="@+id/gl_vertical"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.8"/>

    <android.support.constraint.Guideline
        android:id="@+id/gl_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.8" />

    <ImageView
        android:src="@mipmap/ic_launcher"
        app:layout_constraintLeft_toRightOf="@id/gl_vertical"
        app:layout_constraintTop_toBottomOf="@id/gl_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>
image
Barrier

与 GuideLine 差不多,但是比它更灵活,可以用来约束多个布局,且自动匹配最大最小值进行约束。

Barrier 有两个基本属性:

  • barrierDirection

    取值可为 top, bottom, left, right, start, end

    用于约定栅栏拦截的 View 方向,假设说要拦截的 View 在右侧,这个属性就应该为 right 或者 end

  • constraint_referenced_ids

    被栅栏保护,屏蔽起来的 View 集合,直接输入 viewId,用逗号分隔即可;barrier 会根据宽度或者高度最大的那个 View 来设置栅栏的边界

可能会有些抽象,我们在开发时可能会遇到一种比较蛋疼的需求:

姓名、性别、出生日期、手机号等字段从上到下一字排开,但是每个字段对应的值要保证彼此左侧对齐

讲道理以前这种布局我一直不知道怎么画,但是现在有了 barrier 以后这问题就迎刃而解了。我们可以用 barrier 将左侧的那些字段与右侧的值拦截开,barrier 会自动识别最宽的那个字段,并将之作为 barrier 的宽度,之后每个值都用 barrier 来制造约束就可以了。

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="姓名:"/>

    <TextView
        android:id="@+id/tv_gender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_name"
        android:text="性别:"/>

    <TextView
        android:id="@+id/tv_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_gender"
        android:text="手机号:"/>

    <TextView
        android:id="@+id/tv_birthday"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_phone"
        android:text="出生日期:"/>

    <android.support.constraint.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="end"
        app:constraint_referenced_ids="tv_name,tv_phone,tv_gender,tv_birthday"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        android:text="易烊千玺"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_name"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        android:text="男"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        app:layout_constraintTop_toBottomOf="@id/tv_gender"
        android:text="13800138000"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        app:layout_constraintTop_toBottomOf="@id/tv_phone"
        android:text="2000年1月1日" />

</android.support.constraint.ConstraintLayout>

显示效果如图,很完美有没有?

image
Group

Group 是一个组,用来批量控制 View 的显示与隐藏;但是注意这不是个 ViewGroup,它只是一个不执行绘制的 View,和 barrier 一样,它有一个 constraint_referenced_ids 的属性,可以将需要隐藏的 ViewId 丢进去,在需要的时候将其批量隐藏即可。

还通过上面的例子,假设现在要把性别一栏隐藏掉:

<android.support.constraint.Group
    app:constraint_referenced_ids="tv_gender,tv_sex_value"
    android:visibility="gone"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

通过将性别的 key 和 value 的 id 都放进去,将其设置为 gone,则可以将该组实现隐藏:

image

但是,使用 Group 控制可见性是有坑的:

  1. 和以前用的ViewGroup有一点不同,以前用ViewGroup约束View的时候,外层ViewGroup设置成可见,里层View设置成不可见是可以生效的,但是用Group就不能。Group约束的元素的可见性始终一致。

  2. 调用Group的setVisibility方法不会立即对它约束对子View生效,而是要等到Group所在的ConstrainLayout调用preLayout方法时才会生效。preLayout只有在第一次layout和布局发生变化时才会调用。

Optimizer优化

可以通过将标签app:layout_optimizationLevel元素添加到 ConstraintLayout 来决定应用哪些优化

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_optimizationLevel="standard|dimensions|chains"/>
  • none: 不执行优化
  • standard: 默认,仅优化直接和障碍约束
  • direct: 优化直接约束
  • barrier: 优化障碍约束
  • chain:优化链条约束
  • dimensions:优化维度测量,减少匹配约束元素的度量数量

参考文章

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

推荐阅读更多精彩内容