ConstraintLayout使用

ConstraintLayout在AS2.2时候就有了,2.3时代作为了AS EmptyActivity模板的默认xml,现做如下如下记录


转自:http://blog.coderclock.com/2017/04/09/android/android-constraintlayout/


1- 基本使用:

  • ConstraintLayout最大的好处在于让我们通过拖控件的形式进行布局,并且不用担心适配问题。
constraint_1.png

我们先关注 下部分那个TextView
xml 里反应出的代码如下

constraint_2.png
<TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textSize="20sp"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginRight="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintVertical_bias="0.802" />

app:layout_constraintBottom_toBottomOf=”parent”
意思是TextView底部的布局约束是位于parent的底部,
parent是指包裹着它的ConstraintLayout,
也可以设置指定某个控件的id,其他类似的属性就不再赘述,以上四个约束联合起来便实现了Button的居中,ConstraintLayout总共有下面这些同类的属性:

constraint_3.png

你会发现ConstraintLayout非常灵活的把RelativeLayout的活给干了,关于left、right、top、bottom、start、end、baseline的基准可以参照下图:

constraint_4.png

现:欲增加一个Btn 与原有Btn底部对齐,且 在原有Btn 右侧,只需按如下写:

Constraint_5.gif

xml里表现如下:

<Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button2"
        android:layout_marginLeft="32dp"
        app:layout_constraintLeft_toRightOf="@id/button"
        app:layout_constraintBottom_toBottomOf="@id/button"
        />

至此,button2 依赖了button后,随着button移动,button2也移动,

1.1 Widgets dimension constraints

The dimension of the widgets can be specified by setting the android:layout_width and android:layout_height attributes in 3 different ways:

  • Using a specific dimension (either a literal value such as 123dp or a Dimension reference)
  • Using WRAP_CONTENT, which will ask the widget to compute its own size
  • Using 0dp, which is the equivalent of "MATCH_CONSTRAINT"
    组件 的尺寸有如下三种设置方法:
  • 固定大小
  • WRAP_CONTENT,让系统让组件自己决定大小
  • 0dp 相当于 MATCH_CONSTRAINT
1.2 Dimensions constraints

Minimum dimensions on ConstraintLayout
You can define minimum sizes for the ConstraintLayout itself:

android:minWidth set the minimum width for the layout
android:minHeight set the minimum height for the layout
Those minimum dimensions will be used by ConstraintLayout when its dimensions are set to WRAP_CONTENT.

1.2 约束尺寸

你可以给ConstraintLayout自己设置最小尺寸,组件的最小尺寸,将在组件被设置为WRAP_CONTENT 时 生效

2- 一些Margin属性:

除了Android常见的各种android:layout_marginXXX外,ConstraintLayout自行添加了如下属性:

constraint_6.png

这些设置生效于当依赖的约束对象被设置visibility为gone时,非常简单,读者自行设置实践对比即可,这里就不展示效果了。

0418 UPDATE

https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html

2.1 Visibility behavior 关于View的Gone:

ConstraintLayout has a specific handling of widgets being marked as View.GONE.
对于Gone的组件ConstraintLayout 有一套特殊的处理方式
GONE widgets, as usual, are not going to be displayed and are not part of the layout itself (i.e. their actual dimensions will not be changed if marked as GONE).
通常情况下Gone的组件,是不被展示而且 不作为layout本身的一部分的(也就是说:这些Gone的组件,一旦给了Gone属性,其本身的尺寸将不会改变)
But in terms of the layout computations, GONE widgets are still part of it, with an important distinction:
但是,在layout计算方面,Gone的组件,仍然是layout计算的一部分,这里有一个重要区别:

  • For the layout pass, their dimension will be considered as if zero (basically, they will be resolved to a point)
    • 为了layout 能通过,Gone的组件的 尺寸将被认为是 0 ,(基本上,他们会被当做一个点来处理,笔者记: 这都在layout的preview里可以直观看到)
  • If they have constraints to other widgets they will still be respected, but any margins will be as if equals to zero
    • 如果,某个Gone的组件 和其他组件之间有约束,那么这个Gone的组件仍然是被系统重视的,但是,他的任何margins 会被认为 是 0
constraint_gone_margin.png

This specific behavior allows to build layouts where you can temporarily mark widgets as being GONE, without breaking the layout (Fig. 6), which can be particularly useful when doing simple layout animations.

ConstraintLayout的这种特殊的行为,造成了 它允许你在布局的时候,能临时的把某个组件Gone,而不需要打破整个layout布局,(见上图),这一点,在实现一下简单的layout动画时,尤为常用
**Note: **The margin used will be the margin that B had defined when connecting to A (see Fig. 6 for an example). In some cases, this might not be the margin you want (e.g. A had a 100dp margin to the side of its container, B only a 16dp to A, marking A as gone, B will have a margin of 16dp to the container). For this reason, you can specify an alternate margin value to be used when the connection is to a widget being marked as gone (see the section above about the gone margin attributes).
注意:<font color=#00ffff size=72>color=#00ffff</font>
真正被使用的margin 实际上是: B 在与 A 建立联系时,给B设置的margin.
在某些情况下,这可能不是你想咬的margin值(举个栗子: A 有一个100dp 的margin到他的parent容器,B 左margin A 16dp,现在,让A Gone,这时候,B 将margin parent容器 16dp,这特么就尴尬了),为了解决这个问题,你可以特别的声明一个margin 值 即layout_goneMarngLeft = 100dp(笔者记:个人认为,没测试)专门又来解决 当自己联系的组件被Gone时,margin 不准的情况,具体的见上文蓝色文字链接.

3- Basic 属性:

Bias属性,ConstraintLayout新增了如下两个属性用于控制控件在水平和垂直方向在屏幕上的偏移比例,
在本文开头的TextView 中可以看到这样一条属性: app:layout_constraintVertical_bias="0.802"

只有:当为目标控件设置好横纵向的约束时:

  • app:layout_constraintLeft_toLeftOf=”parent”
  • app:layout_constraintRight_toRightOf=”parent”
  • app:layout_constraintTop_toTopOf=”parent”
  • app:layout_constraintBottom_toBottomOf=”parent”

这个两个属性才会生效
注意,这里需要上下左右都有约束,否则basic 属性没用的.
实际操作过程中,你会发现对着设置好横纵向约束的Button进行拖动,布局中的layout_constraintHorizontal_bias和layout_constraintVertical_bias会一直发生相应的变化,如果你需要Button居中,那么直接将这两个属性的参数值设置为0.5即可。

4- 进阶使用:

0526 UPDATE


http://www.jianshu.com/p/32a0a6e0a98a


视图尺寸

ConstraintLayout也支持自动填充宽高, 把宽高设置为0dp会根据位置自动填充. 如, Large按钮, 左侧与Small按钮的左侧对齐, 右侧与constraintLayout(父控件)的右侧对齐, 宽度设置为0dp, 则会填充全部空位.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    android:id="@+id/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/small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Small"
        app:layout_constraintStart_toStartOf="@id/constraintLayout"
        app:layout_constraintTop_toTopOf="@id/constraintLayout"/>

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Large"
        app:layout_constraintBottom_toBottomOf="@id/constraintLayout"
        app:layout_constraintEnd_toEndOf="@id/constraintLayout"
        app:layout_constraintStart_toEndOf="@id/small"
        app:layout_constraintTop_toTopOf="@id/constraintLayout"/>

</android.support.constraint.ConstraintLayout>
width_0.png

View的尺寸

我们已经讨论了许多关于View如何放置的问题。现在我们来讨论下关于View尺寸的问题。关于为View指定尺寸,ConstraintLayout的方式可能与你以往使用的不大一样。ConstraintLayout提供了三种方式用于指定子View的尺寸:

Exact: 为子View指定一个确切的尺寸。
将layout_width或layou_height设为一个非零尺寸值(xx dp)即可

Wrap Content: 使子View的尺寸正好“包裹”子View的内容
将layout_width或layout_heigth设为wrap_content即可

Any Size: 让子View填满父容器的剩余空间
将layout_width或layout_heigth设为0dp即可

什么鬼!match_parent跑哪去了?实际上ConstrainLayout不支持match_parent,至于为什么,后文会进行解释。简单的说就是Any Size就已经实现了match_parent的功能。

我们来看一个例子:

<ConstraintLayout
  xmlns:android="..."
  xmlns:app="..."
  android:id="@+id/constraintLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
    android:id="@+id/button_cancel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="@+id/constraintLayout"
    app:layout_constraintTop_toTopOf="@+id/constraintLayout"/>

  <Button
    android:id="@+id/button_next"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toEndOf="@+id/button_cancel"
    app:layout_constraintEnd_toEndOf="@+id/constraintLayout"
    app:layout_constraintTop_toTopOf="@+id/constraintLayout"
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout" />

</ConstraintLayout>

我们可以看到,button_next在指定尺寸时,使用了Any Size方式:它的layout_width被设为了0dp,这意味着它会在水平方向填满父布局的剩余可用空间。显示效果如下:

width_00.png

ConstraintLayout也支持自动填充宽高, 把宽高设置为0dp会根据位置自动填充. 如, Large按钮, 左侧与Small按钮的左侧对齐, 右侧与constraintLayout(父控件)的右侧对齐, 宽度设置为0dp, 则会填充全部空位.

4.1 宽高比:

补充一个关于ConstraintLayout的知识点,与其他Layout不同之处在于,它的layout_width和layout_height不支持设置match_parent,其属性取值只有以下三种情况:

  • wrap_content;
  • 指定具体dp值;
  • 0dp(match_constraint),代表填充约束之意,注意不要以为和match_parent是一样的;

想想如果没有ConstraintLayout,我们要让一个控件的宽高按某个比例进行布局应该怎么做?有了ConstraintLayout后,我们可以使用layout_constraintDimentionRatio属性设置宽高比例,前提是目标控件的layout_width和layout_height至少有一个设置为0dp,如下让一个ImageView宽高按照2:1的比例显示:


constraint_7.png

layout_constraintDimentionRatio默认参数比例是指宽:高,变成高:宽可以设app:layout_constraintDimensionRatio=”H,2:1”。其效果与:
app:layout_constraintDimensionRatio=”1:2”是一样的

You can also use ratio if both dimensions are set to MATCH_CONSTRAINT (0dp). In this case the system sets the largest dimensions the satisfies all constraints and maintains the aspect ratio specified. To constrain one specific side based on the dimensions of another.
You can pre append W," or H, to constrain the width or height respectively. For example, If one dimension is constrained by two targets (e.g. width is 0dp and centered on parent) you can indicate which side should be constrained, by adding the letter W (for constraining the width) or H (for constraining the height) in front of the ratio, separated by a comma:
我们也可以把长宽,都设为0dp,再给组件设置app:layout_constraintDimensionRatio,在这种情况下,系统将给组件设置一个能满足所有约束且维护给定长宽比 的最大尺寸

我们也可通过附加:W 或 H,来在宽高方向上分别限制,例如:
假设,一个尺寸被两个目标约束:(例如:宽度0dp,居parent 容器中央),你可以通过W 或 H 来指明,到底在那个方向来约束宽高比,如下:

 <Button android:layout_width="0dp"
                   android:layout_height="0dp"
                   app:layout_constraintDimensionRatio="H,16:9"
                   app:layout_constraintBottom_toBottomOf="parent"
                   app:layout_constraintTop_toTopOf="parent"/>

这个Btn 高度将遵循16:9,同时btn宽度将充满Constrants

Constraint_ratio_H.png
Constraint_ratio_H_2.png

4.1 链 式的使用 :

ConstraintLayout的链条(Chains)特性非常强大,在没有ConstraintLayout之前,线性布局我们主要都依靠LinearLayout来完成,有了ConstraintLayout之后,它把LinearLayout的活也干了,例如要把按钮水平排成一行,可以这样操作:

Constraint_8.gif

这样ButtonA、B、C就在水平方向形成了一条Chain,并且底部对齐。回去看xml文件,会见到ButtonA新增app:layout_constraintHorizontal_chainStyle的属性设置,这个属性在一条Chain中只会出现在第一个控件中,这个控件是整条Chain的Head。

Constraint_chain_instruction.png

NOTE: AS 2.3 中并未出现:app:layout_constraintHorizontal_chainStyle,最后自己手动加上了这个属性,其有三个可取的值:

  • packed
  • spread
  • spread_inside
    效果分别如下:
Constraint_chain_packed_8.png
Constraint_chain_sparead_8.png
Constraint_chain_spread_inside.png

默认效果是这样的:

Constraint_chain_default_8.png

可以看出:默认效果 和 sparead 是一样的
笔者认为,这里的这种布局方式 可以 参考 html 里的flex 布局
附上 三个 Button 的xml 代码:

<?xml version="1.0" encoding="utf-8"?>
<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="match_parent">


    <Button
        android:id="@+id/buttona"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button a"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/buttonb"
        app:layout_constraintVertical_bias="0.203"

        />

    <Button
        android:id="@+id/buttonb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button b"
        app:layout_constraintBottom_toBottomOf="@+id/buttona"
        app:layout_constraintRight_toLeftOf="@+id/buttonc"
        app:layout_constraintLeft_toRightOf="@+id/buttona"

        />

    <Button
        android:id="@+id/buttonc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button c"
        app:layout_constraintBottom_toBottomOf="@+id/buttonb"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/buttonb"

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

除了水平方向的layout_constraintHorizontal_chainStyle外还有垂直方向的layout_constraintVertical_chainStyle,两者均有spread,spread_inside,packed这三种取值,如果将控件的layout_width和layout_height设置成为0dp,还可以配合layout_constraintHorizontal_weight、layout_constraintVertical_weight两个属性实现和LinearLayout中设置layout_weight相同的效果,具体的操作这里就不再展示了,下面一张图告诉你Chain的强大之处。

Constraint_chain_instruction2.png

5- Guideline

如果我们需要对着屏幕的中轴线进行布局,就可以使用到Guideline进行操作,例如下面两个Button分别分布在中轴线的左右两侧

Constraint_guildline.gif

Guideline也分为垂直和水平两种,并且支持设置在屏幕中所处的位置,可以使用如下三种模式:

  • layout_constraintGuide_begin
  • layout_constraintGuide_end设置具体dp值,
    也可以使用
  • layout_constraintGuide_percent来设置比例。
    实际上它也只是一个辅助我们布局的View而已,其源码内部实现也非常简单,并且默认设置了visibility为gone,关于ConstraintLayout的进阶使用便介绍到这里。

总结:

使用优势:

  • 高效布局,Android这么多年以来真正意义上的实现了所见即所得的拖曳方式布局,极大的提高开发效率;
  • 轻松灵活的实现复杂的布局;
  • 解决多重布局嵌套问题,通过前面介绍你会发现ConstraintLayout真的是非常灵活,可以很大程度的避免Layout之间的嵌套;
  • 满足屏幕适配的需求,想想没有ConstraintLayout之前的拖曳式布局,你就知道有多恶心;

最佳实践:

ConstraintLayout最开始出来就有很多开发者盼着完全依赖于拖曳方式实现布局,而在实际操作过程中,完全通过拖曳其实效率反倒是会打折扣,在此建议是拖曳方式和xml编码相结合才是最佳实践的方式。

推荐阅读更多精彩内容