ConstraintLayout基础介绍

2,492 阅读9分钟

自去年Google I/O 大会发布ConstraintLayout至今,已有一年多的时间,但是并没有普及开来,了解过ConstraintLayout布局的人知道,它的性能的确提升了不少。在前不久,Google 开发者博客发布了一篇文章Understanding the performance benefits of ConstraintLayout中文地址)详细分析ConstraintLayout性能的优势,感兴趣的朋友可以去看看。

当然自己之前也没有认识到ConstraintLayout布局的性能优势,所以从这篇文章开始由浅入深详细介绍ConstraintLayout的属性及使用,也让自己对ConstraintLayout也有一个更全面的认识,今天的这篇文章主要介绍布局的一些属性,学习地址是Google文档

配置

在使用ConstraintLayout之前我们需要在我们app下的gradle文件添加ConstraintLayout依赖,截止到目前ConstraintLayout的最新版本是1.0.2.

dependencies {
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
}

#Relative positioning
相对定位的效果和RelativeLayout布局有异曲同工之处,只不过要比RelativeLayout强大。约束能允许我们指定一个控件相对于另一个控件的位置,通过一些属性我们可以对组件进行水平或者垂直排列。例如当我们使用含有Left, Right, Start 或者End关键词词属性进行定位时即是对组件进行水平方向排列,同理 top, bottom 和 text baseline(文字的基线位置)就是垂直方向排列。具体相对定位的一些属性组合如下

  • layout_constraintLeft_toLeftOf
    当前组件的左边在某组件的左边,即左对齐
  • layout_constraintLeft_toRightOf
    当前组件的左边在某组件的右边,即控件的左边和约束控件的右边对齐
  • layout_constraintRight_toLeftOf
    当前组件的右边在某组件的左边
  • layout_constraintRight_toRightOf
    当前组件的右边在某组件的右边
  • layout_constraintTop_toTopOf
    当前组件的上边和某组件的上边对其
  • layout_constraintTop_toBottomOf
    当前组件的上边在某组件的下边
  • layout_constraintBottom_toTopOf
    当前组件的下边在某组件的上边
  • layout_constraintBottom_toBottomOf
    当前组件的下边在某组件的下边
  • layout_constraintBaseline_toBaselineOf
    当前组件的基线位置和某组件的基线位置对其(很少用)
  • layout_constraintStart_toEndOf
  • layout_constraintStart_toStartOf
  • layout_constraintEnd_toStartOf
  • layout_constraintEnd_toEndOf

对于上面这些属性的值有两种,一种就是同层级组件ID,还有就是parent,当值为parent时即是相对于父布局进行定位。

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="textView1" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="textView2"
        app:layout_constraintLeft_toRightOf="@+id/textView1" />
</android.support.constraint.ConstraintLayout>

例如上面的布局,我们使用app:layout_constraintLeft_toRightOf="@+id/textView1"将textView2的左边和textView1的右边对齐,效果图

image.png
image.png

那么当textView2属性设置为

    app:layout_constraintTop_toBottomOf="@+id/textView1"
    app:layout_constraintLeft_toRightOf="@+id/textView1"

效果图:

image.png
image.png

当textView1设置属性 app:layout_constraintRight_toLeftOf="parent"
textView2设置 app:layout_constraintLeft_toLeftOf="parent"时,

效果图:

image.png
image.png

Margins

对于margin值我们都不陌生,因为我们经常使用,有以下几种

  • android:layout_marginStart
  • android:layout_marginEnd
  • android:layout_marginLeft
  • android:layout_marginTop
  • android:layout_marginRight
  • android:layout_marginBottom

需要注意的是此margin只对于设置了约束的地方起作用,如下布局

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="textView1" />
</android.support.constraint.ConstraintLayout>

我们只设置了TextView的左边和父布局左边约束,当我们设置了左边和上边的margin值都为10时,发现只有左边的边距生效,而上边的变化没有发生作用,这也就验证了边距只对有约束行为的地方起作用。

除了我们常用的margin设置属性外,ConstraintLayout 还提供了一些特有的margin设置。先看下面布局

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="textView1"
        app:layout_constraintLeft_toLeftOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="textView2"
        app:layout_constraintLeft_toRightOf="@+id/textView1" />
</android.support.constraint.ConstraintLayout>

上面textview2在textview1的右边,那么当我们由于某种需求将textview1设置了 View.GONE隐藏该控件,那么此时textview2将跑到位置将显示在左上角,如果我们在隐藏textview时而保持textview2位置不变。在之前的应用中能稍微比较麻烦一点,但是ConstraintLayout 给我们提供了layout_goneMargin**类的属性,该属性是表示约束隐藏时的margin值。例如上面的textView2我们增加app:layout_goneMarginLeft="100dp"属性就可以保持当约束textView1隐藏时而保持textview2位置不变。

需要注意的一点是如果某个约束设置了View.GONE,相当于这个组件宽和高为0,约束布局中相当于一个点,其他设置的约束依然有效。

Centering positioning and bias

    <TextView
        android:id="@+id/textView1"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="textView1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

如果约束布局中我们添加textview并设左边和父布局左边,右边和父布局右边对齐,并设置宽度100dp,那么此时效果是怎样的呢。我们发现textview居中显示了,这就是约束布局的居中定位。
其实我们可以理解为父布局对textview左边和右边都有一个拉力,由于默认这个力大小相同就到显示到中间位置。

image.png
image.png

在上面由于两个相反的约束,而使组件居中,在协同布局中还提供了bias,通过属性layout_constraintHorizontal_bias或者layout_constraintVertical_bias设置组件偏向水平或者垂直的某一测,,默认情况下该值是0.5(50%).例如我们设置textview属性

        app:layout_constraintHorizontal_bias="0.3"

此时组件偏向左边

image.png
image.png

Dimensions constraints

当我们的协调布局的子组件设置wrap_content时,我们可以通过android:minWidth或者android:minHeight 属性设置最小宽度或者高度的约束。minWidth/minHeight只有宽度或者高度为wrap_content才有作用。

对于android:layout_height /android:layout_width属性,它的值只有三种情况

  • 使用指定的大小或者指定大小的引用,如100dp
  • WRAP_CONTENT,此时根据内容自己计算大小
  • 0dp,该值相当于MATCH_CONSTRAINT。

需要注意的是在约束布局中MATCH_PARENT 属性值不在支持,例如在上面的TextView我们设置layout_width分别是100dp,0dp,0dp(marginLeft :20dp)大致效果如图a,b,c

image.png
image.png

Ratio

比例约束可以约束我们控件的宽高比,例如下面示例

    <TextView
        android:id="@+id/textView1"
        android:layout_width="100dp"
        android:layout_height="0dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="textView1"
        app:layout_constraintDimensionRatio="2:1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

设置宽度为100dp,高度为0dp,此时设置了宽高比是2:1,则宽度会自动约束调整为50dp。
如果我们将上面的宽和高度调换,宽为0dp,高100dp,此时最终宽度为200,高度100(宽:高=2:1)

在上面介绍的的宽高比约束是单维度的,那么如果我们的宽和高都有约束,都设置为0dp,在这种情况下,系统会使用满足所有约束条件和比率的最大尺寸。当然我们也可以在比例值前面加 W 或者 H 来分别约束宽度或者高度,如H,2:1。

Chains

链是一种特殊的约束它能让多个该链连接的 多个Views 平分剩余空间位置
如下我们创建一个水平链

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/A"
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:background="@color/btnnormal"
        android:gravity="center"
        android:text="A"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/B" />

    <TextView
        android:id="@+id/B"
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="B"
        app:layout_constraintLeft_toRightOf="@+id/A"
        app:layout_constraintRight_toLeftOf="@+id/C" />

    <TextView
        android:id="@+id/C"
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:background="@color/btnnormal"
        android:gravity="center"
        android:text="C"
        app:layout_constraintLeft_toRightOf="@+id/B"
        app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>

上面代码的效果图如下,A和C相对于父组件的左边和右边有一个约束,A和B,B和C之间两两相互约束,我们还需要知道的是对我们称链的第一个元素组件为链头。如下图A就是该链的链头

image.png
image.png

链的模式

对于链头我们可以通过属性layout_constraintHorizontal_chainStyle(layout_constraintVertical_chainStyle)设置,该属性有三个值,spread ,spread_inside ,packed

  • CHAIN_SPREAD
    默认情况下该属性默认值是spread 。它的间隙将平分剩余空间,如上图所示
  • CHAIN_SPREAD_INSIDE
    spread_inside值会把两边最边缘的两个 View 靠边显示,然后让剩余的 Views 在剩余的空间内平分间隙。当面设置spread_inside时效果图如下

image.png
image.png

  • CHAIN_PACKED
    还有一种值是packed ,它表示将view之间紧挨着显示,并且全体居中显示,设置该模式后效果图如下

image.png
image.png

除此之外,我们可以通过设置layout_constraintHorizontal_bias属性来调整整体的位置。默认情况居中,也就是该值为0.5,例如我们将该值设置0.3,则实现效果如下

image.png
image.png

  • Weighted chain
    在官方文档中还介绍了一种模式是权重模式,在CHAIN_SPREAD 模式中,如果我们设置控件的宽或者高设置MATCH_CONSTRAINT即0dp,它们将按权重平分占满父控件的宽或者高,对于权重的设置是属性和我们线性布局设置权重达到一样的效果,例如在上面的三个TextView,我们设置宽度都为0dp,并设置链样式为spread(或spread_inside),此时效果 图如下

image.png
image.png

如果我们给A和C设置下面属性

        app:layout_constraintHorizontal_weight="1"

给B设置属性

        app:layout_constraintHorizontal_weight="2"

那么此时A,B,C的宽度为1:2:1比例占满父组件宽度。设置权重时链样式不能设置为packed (设置后宽度会收缩为0)

参照线Guideline

Guideline用于辅助我们对View进行定位,以及设置约束,它不会再真正的显示,只是起到辅助作用,常用属性如下

  • android:orientation
    该属性可以指定辅助线是垂直还是水平线,它有两个值即vertical,horizontal,
  • layout_constraintGuide_begin/layout_constraintGuide_end
    用来设置对齐父组件的 start 或者end边缘的距离,
  • layout_constraintGuide_percent
    该值是0.0到1.0之间,用来设置辅助线在父布局的位置,例如设置0.5,就相当于在父视图宽度的中间50%。

      <android.support.constraint.Guideline
          android:id="@+id/guideline_h"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:orientation="horizontal"
          app:layout_constraintGuide_percent="0.5" />
    
      <android.support.constraint.Guideline
          android:id="@+id/guideline_v"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:orientation="vertical"
          app:layout_constraintGuide_percent="0.5" />
    
      <TextView
          android:id="@+id/tvguide"
          android:layout_width="50dp"
          android:layout_height="50dp"
          android:background="@color/red"
          android:gravity="center"
          android:text="Guide"
          app:layout_constraintBottom_toTopOf="@+id/guideline_h"
          app:layout_constraintLeft_toLeftOf="@+id/guideline_v" />

    在上面我们在约束布局中创建两个辅助线,分别是垂直和水平的辅助线,并将textView的下面和水平辅助线的上面对齐,将TextView的左边和垂直辅助线左边对齐。这样我们可以使用辅助线任意控制View的约束位置。

image.png
image.png

好了今天ConstraintLayout的基础知识就介绍到这里了,下一篇文章将介绍ConstraintLayout布局中Behavior的相关知识,如文中有错误欢迎指出。Hava a wonderful day。