深入理解ConstraintLayout之使用姿势

**本文译自:http://wiresareobsolete.com/2016/07/constraintlayout-part-1/ **
本文介绍了ConstraintLayout的各种使用姿势,下一篇则会对其实现原理做出分析。在此感谢原作者的分享:)

在2016年的Google I/O大会中,Google为Android开发者带来了新的福利——ConstraintLayout(约束布局)和layout editor(布局编辑器)。使用ConstraintLayout,能够让我们应用的布局更加“扁平化”,提升我们的开发体验。而layout editor是Android Studio 2.2新增的一个可视化布局工具,使用它可以通过拖动UI控件来进行“约束布局”,还可以实时显示ConstraintLayout的布局效果。

译者注:使用ConstraintLayout并不需要Android Studio 2.2或以上版本,但是要使用layout editor需要将Android Studio先升级到2.2或以上版本。使用ConstraintLayout需要我们添加如下依赖:

compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha4'

本篇文章会介绍如何使用ConstraintLayout的各种特性。在下篇文章中,我们会分析ConstraintLayout的工作原理,理解了工作原理,我们对它的运用也会更加得心应手。

先让我们从约束(constraint)是什么开始…

约束的类型

根据官方的介绍,一个对View的约束描述的是”View在布局中相对于其他元素的位置是怎样的”。在一个约束关系中,至少有两个主角,一个我们称之为source(源),另一个我们称之为target(目标)。其中source的位置依赖于target。我们可以这样理解:约束以某种方式将source与target连接了起来,这样source相对于target的位置便是固定的了。 我们可以将source和target看作是位于View上的点,称之为“锚点(anchor point)”。ConstraintLayout中的每个View都支持将如下锚点作为约束关系中的source或target:

  • top, bottom, left(start), right(end)
  • centerX, centerY
  • baseline

在XML文件中,是这样描述一个约束的:

layout_constraint[SourceAnchor]_[TargetAnchor]="[TargetId]"

上面的描述可能比较抽象,我们来看一个例子。这个例子建立了button_cancel的最右端(end)和button_next的最左端(start)的约束。相应的XML文件如下:

<Button android:id="@+id/button_cancel" 
  …​ />

<Button android:id="@+id/button_next"   
  app:layout_constraintStart_toEndOf="@+id/button_cancel" 
  …​ />

上面的描述的约束会使得button_cancel和button_next布局在同一水平线上,并且button_next位于button_cancel的右边。

在定义一个约束时,我们需要注意为约束关系中涉及到的View指定一个id属性,ConstraintLayout通过id属性才能定位到约束关系中的target或是source。

我们来看一个完整的描述约束关系的XML文件:

<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: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"
    android:layout_marginStart="16dp"      
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
    android:layout_marginBottom="16dp" /> 

  <Button android:id="@+id/button_next" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    app:layout_constraintStart_toEndOf="@+id/button_cancel" 
    android:layout_marginStart="16dp" 
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
    android:layout_marginBottom="16dp"/>

</android.support.constraint.ConstraintLayout>

在上面的例子中,建立了4个约束关系,其中button_cancel中与button_next中分别定义了两个约束关系:

  • button_cancel在父View(ConstraintLayout)的底部、靠左,这是button_cancel定义的两个约束;
  • button_next在button_cancel的右边,button_next在父View的底部,这是button_next定义的两个约束。

显示效果如下:

《深入理解ConstraintLayout之使用姿势》

在上面的例子中,我们建立了Button如何与父容器对齐的约束,建立约束的语法与上面介绍的相同,只不过这里的targetId是父容器(ConstraintLayout)的id。

大家可能从上个例子中注意到了ConstraintLayout支持margin。默认情况下,建立了约束关系的两个View会紧紧挨着彼此,若我们想要它们之间保持距离,就可以定义margin。

看到这里你可能想说,ConstraintLayout不就是RelativeLayout吗?别急,我们接着往下看。

偏置约束(Biasing Constraints)

当一个View与同一轴向(水平/垂直)的2个target建立了约束,默认情况下它会被放在两个target的正中间。下面的XML文件实现了把Button在父容器中的正中央:

<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:id="@+id/constraintLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

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

</android.support.constraint.ConstraintLayout>

显示效果如下:

《深入理解ConstraintLayout之使用姿势》

但是有时候我们不想让Button居中显示,这时我们可以通过偏置(bias)属性来实现。请看下面的XML文件:

<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:id="@+id/constraintLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button"
    app:layout_constraintTop_toTopOf="@+id/constraintLayout"
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
    app:layout_constraintStart_toStartOf="@+id/constraintLayout"
    app:layout_constraintEnd_toEndOf="@+id/constraintLayout"
    app:layout_constraintHorizontal_bias="0.25"
    app:layout_constraintVertical_bias="0.25" />

</android.support.constraint.ConstraintLayout>

实现的效果如下:

《深入理解ConstraintLayout之使用姿势》

上图实现的是:Button偏离父容器左边缘的距离为父容器宽度的25%,偏离父容器上边缘的距离为父容器高度的25%。实现了这个效果的就是上面XML文件中的”layout_XXX_bias”属性。肿么样,这个bias属性是不是比layout_weight强大多了?实际上,当我们不提供bias属性时,默认的水平与垂直bias都是0.5,所以Button默认会在正中央。

正是通过”layout_XXX_bias”等强大属性,ConstraintLayout使得应用布局尽可能”扁平化“。实际上,所有其他布局管理器能做的,ConstraintLayout都能做到,它甚至可以同时具备好几种布局管理器的功能。

锚向指示线(Anchoring to Guidelines)

当需要一个任意位置的锚点时,我们可以使用指示线(guideline)。指示线实际上是View的子类,加入布局的方式也和普通View一样。指示线有如下特殊属性:

  • 它们的测量宽高总是0;
  • 它们的可见性总是View.GONE。

指示线的存在只是为了为其他View定义一个水平或垂直的锚点。我们来看一个例子:

<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:id="@+id/constraintLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.constraint.Guideline
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/guideline"
    android:orientation="vertical"
    app:layout_constraintGuide_begin="72dp" />

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

  <Button
    android:id="@+id/button_next"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="@+id/guideline"
    app:layout_constraintTop_toTopOf="@+id/constraintLayout"
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
    app:layout_constraintVertical_bias="0.75" />

</android.support.constraint.ConstraintLayout>

聪明的你一定猜到了上面文件的布局效果:

《深入理解ConstraintLayout之使用姿势》

在上面的文件中我们设置了一个距离父容器左边缘72dp的垂直指示线。这样一来,Button就能够使用这个指示线作为它的target。指示线可以使用以下三个属性之一:

  • layout_constraintGuide_begin:指示线距离父容器左边缘的绝对距离
  • layout_constraintGuide_end:指示线距离父容器右边缘的绝对距离
  • layout_constraintGuide_Percent:指示线距离父容器左边缘的距离,这个属性的值是一个百分比,表示距离占父容器宽度的比例

在当前的alpha版本中存在一个关于指示线的bug:在XML文件中,指示线的定义必须放在引用它的View之前,这样才能正常工作。

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的功能。

我们来看一个例子:

<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: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" />

</android.support.constraint.ConstraintLayout>

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

《深入理解ConstraintLayout之使用姿势》

另一个牛逼的属性

一个常规的UI布局需求就是把一个View的尺寸设为特定的宽高比。对于图片来说这个需求更是常见,比如将图片的宽高比设备1:1, 4:3, 16:9等等。通过使用ConstraintLayout,我们无需再创建一个自定义View来实现这个效果,只需使用layout_constraintDimensionRatio属性即可。

使用这个属性时,我们需要固定View的宽或是高。假如我们固定了View的宽,并为其设置了一个宽高比,View的高就会在这个宽高比的约束下随着View的宽变化而变化。使用示例如下:

<?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:id="@+id/constraintLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <ImageView
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:src="@drawable/water"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintLeft_toLeftOf="@+id/constraintLayout"
    app:layout_constraintTop_toTopOf="@+id/constraintLayout"
    app:layout_constraintRight_toRightOf="@+id/constraintLayout"
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
    app:layout_constraintVertical_bias="0.0" />

  <ImageView
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:src="@drawable/grass"
    app:layout_constraintDimensionRatio="4:3"
    app:layout_constraintLeft_toLeftOf="@+id/constraintLayout"
    app:layout_constraintRight_toRightOf="@+id/constraintLayout"
    app:layout_constraintBottom_toBottomOf="@+id/constraintLayout" />

  …​

</android.support.constraint.ConstraintLayout>

在上面的布局文件中我们分别固定了两个ImageView的高和宽,然后将另一个维度的尺寸设置为了0dp,并为两个ImageView分别指定了一个宽高比。显示效果如下:

《深入理解ConstraintLayout之使用姿势》

接下来是什么

现在大家已经见识到了ConstraintLayout的能耐了。在下一篇文章中,我们会探索ConstraintLayout是怎样工作的,通过深入了解其实现机制,我们在运用ConstraintLayout也会更加得心应手。

这里 有关于ConstraintLayout的更多例子

长按或扫描二维码关注我们,让您利用每天等地铁的时间就能学会怎样写出优质app。

《深入理解ConstraintLayout之使用姿势》

    原文作者:absfree
    原文地址: https://www.jianshu.com/p/b406ddc8b913
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞