扁平化布局ConstraintLayout

ConstraintLayout 介绍

ConstraintLayout 是2016 谷歌I/O大会上引入的,用于创建大型复杂的布局,类似于 RelativeLayout ,它提供了一些布局属性来建立它子 View 之间的布局联系,但它比 RelativeLayout 更灵活,更扁平化

ConstraintLayout的优势

  • 它提供了一组布局属性来灵活的组织子View的布局,能有效减少视图层级,使布局更加扁平化,拥有较好的性能
  • 它提供了一些属性能有效兼容不同的手机屏幕的布局结构相同 如 bias、Ratio属性
  • 在 AndroidStudio 上可以结合 Layout Editor 图形编辑器使用,不写一行代码仅通过拖动控件来完成UI布局

ConstraintLayout的使用

通过拖拽控件完成布局在控件不算多,布局不复杂的情况下较方便和高效,但当页面内容较多,布局较复杂的时候,在图形编辑器上面拖动布局往往不那么高效,手写 XML 反而能更精确定位控件更高效完成布局,而往往使用 ConstraintLayout 是用来完成一些较复杂的布局的,所以本文不涉及图形编辑器拖拽控件部分,主要从布局属性方面来讲解在XML灵活使用 ConstraintLayout

使用环境

一 在 AndroidStudio 的 SDK Manager 中安装 ConstraintLayout 和 Solver for ConstraintLayout 包
二 在项目Gradle中添加依赖,最新版本是1.0.2

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

布局属性介绍

在UI布局中由定位尺寸和形状尺寸来最终决定一个控件的显示,表现在ConstraintLayout中定位尺寸主要就是控件在水平和垂直方向上的约束属性,形状尺寸就是

  • wrap_content 自适应内容大小
  • match constraints 类似于match parent
  • fixed 固定单位尺寸

形状属性

ConstraintLayout 控制子 View 的形状大小属性值与传统的 View 有部分不同

还是通过 android:layout_width 和 android:layout_height 来指定,有三种不同的取值:

  • 使用确定的尺寸,比如 48dp
  • 使用 WRAP_CONTENT ,和其他地方的 WRAP_CONTENT 一样
  • 使用 0dp,这个选项等于 “MATCH_CONSTRAINT”,也就是和约束规则指定的宽(高)度一样

例如下图

  • (a) 设置为 wrap_content
  • (b) 设置为 0dp,则 View 的宽度为整个父容器的宽度
  • (c) 设置了margin的情况下的宽度

layout_constraintDimensionRatio 控制宽高比

如果高度和宽度都设置了具体值,则此属性无效,宽高比必须需要高度或者宽度中有一个尺寸为0dp。

  • 当宽度和高度中有且只有一个尺寸为 0dp 时,此 0dp 方向上的具体值由宽高比计算得出例如宽高比为16:9 宽度为 0dp,高度为 wrap_content 或者具体值Xdp,则宽度实际值为 X*16/9
  • 当宽度和高度都为 0dp 时 此时需要确定谁为基准值,谁由基准值通过宽高比得出,默认情况下高度为基准值 如果当宽度和高度都为 0dp ,宽高比为1:2时 系统通过约束得到高度的值为 100dp,则宽度的实际值为 1/2 * 100dp=50dp,但是可以通过在宽高比前面加W或者H来指定需要计算的值,如果取了 H,1:2 则高度值是通过基础值和宽高比来确定的 如果此时宽度根据约束得到是 100dp, 则此时高度为 200dp

除了上面的形状大小尺寸属性外 ,还有如下几个精细控制 View 尺寸的属性(注意:下面这些属性只有宽度或者高度设置为 0dp (MATCH_CONSTRAINT) 的情况下才有效):

  • layout_constraintWidth_default
  • layout_constraintHeight_default 取值为 spread 或者 wrap,默认值为 spread ,占用所有的符合约束的空间;如果取值为 Wrap ,则受所设置的约束限制其尺寸,这个 view 最终尺寸不会超出约束的范围。
  • layout_constraintHeight_max 取值为具体的尺寸
  • layout_constraintHeight_min 取值为具体的尺寸
  • layout_constraintWidth_max 取值为具体的尺寸
  • layout_constraintWidth_min 取值为具体的尺寸

相对定位约束属性

ConstraintLayout 中的特有的定位尺寸(约束属性,不包括传统的 margin 、padding )

  • layout_constraintLeft_toLeftOf
  • layout_constraintLeft_toRightOf
  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintTop_toTopOf
  • layout_constraintStart_toEndOf
  • layout_constraintStart_toStartOf
  • layout_constraintEnd_toEndOf
  • layout_constraintEnd_toStartOf
  • layout_constraintBaseline_toBaselineOf

遵循 layoutconstraint[SourceAnchor][TargetAnchor]=”[TargetId]” 规则

在一个约束关系中,至少有两个主角,一个我们称之为source(源),另一个我们称之为target(目标)。其中source的位置依赖于target。我们可以这样理解:约束以某种方式将source与target连接了起来,这样source相对于target的位置便是固定的了。 我们可以将source和target看作是位于View上的点,称之为“锚点(anchor point)”

比如 要描述一个控件B在另一个控件A的右边

layout_constraintLeft_toRightOf="@+id/A" 

第一个 Left 表示源控件 B 的左侧的锚点,Right 表示目标控件的右侧锚点 “@+id/A” 表示目标控件的 id , 结合起来就是 源控件B左侧的锚点在目标控件A右侧锚点的地方,下面是有 margin 和无 margin 的图示

下图是每个 View 的上下左右以及 baseline 示意图

如果要达到控件A与控件B左侧对齐:

layout_constraintLeft_toLeftOf="@+id/B"

Alignment属性

  • layout_constraintLeft_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintTop_toTopOf

偏移属性

  • layout_constraintHorizontal_bias
  • layout_constraintVertical_bias

试想一个这样的场景 源控件S的左侧约束于目标控件T1的右侧,右侧约束于另一个目标控件T2的左侧,如果目标控件T1与T2之间的宽度空间大于源控件S的宽度,系统是如何最终确定S的位置的?

这种情况下就像使用两个作用力分别从左边和右边来拉住这个源控件S,默认情况下左右两边的力的相等的,最终表现在UI上就是源控件S居中于目标控件T1与T2之间的区域,也就是默认 layout_constraintHorizontal_bias 等于0.5, layout_constraintHorizontal_bias 表示对于所在整个区域的偏移百分比,值0.5就是偏移一半到中间,如果设置成0.3则如下图所示

Guideline指向线属性

可以在布局中使用 Guideline 来辅助约束控件位置,它不计算宽高,不实际渲染在视图上所以不用担心性能,它有下面几个属性

  • layout_constraintGuide_begin 指定距离左(或者上)边开始的固定位置
  • layout_constraintGuide_end 指定距离右(或者下)边开始的固定位置
  • layout_constraintGuide_percent 指定位于布局中所在的百分比
  • orientation 指定方向(是水平线还是垂直线)

如下面的xml代码表示 在垂直方向上的中心线

<android.support.constraint.Guideline
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/guideline2"
        app:layout_constraintGuide_percent="0.5"
        android:orientation="vertical"
        />

有了 Guideline 之后其他控件可以约束于 Guideline 来达到灵活布局的目的

无占位 间距属性

  • layout_goneMarginBottom
  • layout_goneMarginEnd
  • layout_goneMarginLeft
  • layout_goneMarginRight
  • layout_goneMarginStart
  • layout_goneMarginTop

如果一个控件 A 在控件 B 的右侧,而控件B在控件C的右侧,此时如果控件 B 设置为 gone (无占位)则控件 A 对齐于控件 C 的右侧了(这里有点类似于 LinearLayout ),如果需求是当 B 隐藏时 A 距离左边 Xdp 则设置 layout_goneMarginLeft=”Xdp” 即可

注意这个属性要设置在A上面而不是实际隐藏的控件B的上面

链条布局属性(chain)

  • layout_constraintHorizontal_chainStyle
  • layout_constraintVertical_chainStyle
  • layout_constraintHorizontal_weight
  • layout_constraintVertical_weight

Chains 为同一个方向(水平或者垂直)上的多个子 View 提供一个类似群组的概念。其他的方向则可以单独控制。

创建链条

多个 View 相互在同一个方向上双向约束就创建了一个 Chain。什么是双向约束呢? 比如在水平方向上两个 Button A 和 B,如果 A 的右边位于 B 的左边,而 B 的左边位于 A 的右边,则就是一个双向约束。如下图:

在XML中体现为:

A:left_toleftof="parent" rigit_toleftof="B"

B:left_torightof="A" right_torightof="parent"
Chain heads

Chain 的属性由该群组的第一个 View 上的属性所控制(第一个 View 被称之为 Chain head)

水平群组,最左边的 View 为 head, 垂直群组最上面的 View 为 head

chainStyle 是设置到 Chain Head 上的,指定不同的 style 会改变里面所有 View 的布局方式,有如下四种 Style

  • CHAIN_SPREAD 这个是默认的 Style, 里面的所有 View 会分散开布局
    • Weighted chain,在 CHAIN_SPREAD 模式下,如果有些 View 的尺寸设置为 MATCH_CONSTRAINT(0dp),则这些 View 尺寸会占据所有剩余可用的空间,和 LinearLayout weight 类似。
  • CHAIN_SPREAD_INSIDE 两端的两个 View 和 父容器之间不占用多余空间,多余空间在 子 View 之间分散
  • CHAIN_PACKED 这种模式下,所有的子 View 都居中聚集在一起,但是可以设置 bias 属性来控制聚集的位置。

下面是几种模式示意图:

如果多个子View尺寸设置为 0dp ,则这些 View 会平均的占用多余的空间。通过 layout_constraintXXX_weight 属性,可以控制每个 View 所占用的多余空间的比例。例如,对于只有两个 View 的一个水平 Chain,如果每个View 的宽度都设置为 0dp, 第一个 View 的 weight 为 2;第二个 View 的 weight 为 1,则第一个 View 所占用的空间是 第二个 View 的两倍。

最后

熟练掌握 ConstraintLayout 的这些属性可以让我们在构建复杂页面时灵活布局,提高页面整体性能,至于 ConstraintLayout 是如何做到这种扁平化的页面UI树结构的,下篇文章会从源码来分析。
谢谢阅读!