Android ConstraintLayout完全解析


下载ConstraintLayout的支持包

点击SDK Manager,如下所示:


进入Android SDK下载界面,如下所示:


勾选ConstraintLayout for Android和Solver for ConstraintLayout中对应的版本项,点击OK,安装完成。

引用

注意:确保你的Android Studio是2.2或以上版本。

app/build.gradle文件中添加ConstraintLayout的依赖,如下所示。

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

注:如果在Android Studio 3.0以上,可以改成如下依赖

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

Android Studio创建的这个布局如果不是ConstraintLayout,我们可以通过如下操作将它转换成ConstraintLayout。



什么是约束(Constraints)

参考文章(https://constraintlayout.github.io/basics/create_constraint.html) 作者:[Mark Allison]

ConstraintLayout 的核心基础就是创建约束。约束定义了布局内两个组件之间的关系,从而控制组件的布局位置。对于刚接触 ConstraintLayout 但对 RelativeLayout 熟悉的开发者来说,约束布局的工作原理很像 RelativeLayout 中通过创建组件间关系来控制布局。

在 Android Studio 编辑器中创建约束

最容易创建约束布局的方式是通过 Android Studio 中的 design 可视化布局编辑器。例子都通过蓝图 Blueprint 视图来查看展示,我们先简单看看在 Blueprint 视图中的 TextView 。如下所示:


清晰地可以看到 TextView 组件,以及两个箭头符号表示在这个 TextView 组件上存在约束将它对齐到父组件 ConstraintLayout 的左边缘和上边缘。待会再来看它们是如何创建的,还可以看到存在48dp上边距与60dp左边距让父组件 ConstraintLayout 和 TextView 的组件边缘之间保留了一些间隙。选择 TextView组件就会看到如下的缩放和约束锚点。如下所示:


边角上的小正方形是缩放的控制点,通过拖拉这些点就可以对 TextView 进行缩放。但是这个大多数情况并不是很适用,因为使用这种方式进行缩放后的组件将保持固定的尺寸,而我们往往更需要 TextView 根据具体情况响应式大小。

每条边中间的锚点就是约束锚点,我们就是用这个锚点来创建约束的。其中左边和上边的锚点里面有蓝点表示这个锚点已经存在了一个约束,相对的右边和下边的空心锚点则表示没有约束。从这个例子,我们就可以看到 TextView 的布局位置就通过定义约束来对齐了父组件。

任何继承了 TextView 的子组件都拥有另一个锚点:被称为基线(baseline)。这就允许我们通过该锚点来调整组件内的文字对齐基线。选择 TextView 后出现下方按钮,点击其中的 ab 按钮来显示这个锚点。如下所示:

在 TextView 上出现香肠状的控制锚点就是基线约束锚点。我们可以通过给这个锚点添加约束就像下面提到给四个边的约束锚点添加约束一样。

另一个出现的下方按钮中是取消约束按钮(按钮中存在 'x' ),点击将移除该组件上的所有约束。如下所示:


创建锚点,我们只需要简单的从一个组件的锚点,拖动指向到另一个组件 View 的锚点。此处的例子,我们创建另一个 TextView (id 为 textView2 ,原来的那个 id 是 textview),而且 textView2 已有一个对齐父组件左边的约束,我们再创建一个约束,从 textView2 的上边到 textview 的下边。而这个约束就会让 textView2 对齐到 textview 正下方,如下所示:


在此处还要注意,我们创建的约束是从 textView2 的上边到 textView 的下边,当我们选择这两个组件的时候,我们只会看到 textView2 的上边约束锚点存在约束,而 textView 的下边约束锚点是空心的不存在约束。如下所示:


这样的原因是约束是单向的(除非我们谈论的约束是链接 chains ),所以这里例子创建的约束是属于 textView2 的,影响的也是 textView2 的布局位置是相对于 textView 的。因为该约束是只属于 textView2 的,反过来不会影响 textView 的布局位置。

上面讲到的是同级组件间创建约束,而对于一个组件要创建相对于父组件的约束,则只是简单的将约束拖的方向到合适的父组件边缘即可,如下所示:



在 XML 中创建约束

对于想了解在可视化布局下真正的存储的是如何的开发者,以下就是 上面例子的 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"
    android:id="@+id/relativeLayout2">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_marginStart="60dp"
        android:layout_marginTop="48dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="15dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />
</android.support.constraint.ConstraintLayout>

代码中的约束都是以 app:layout_constraint 开头的属性。我们可以看到 ConstraintLayout 中所有子组件都存在这些属性。

注意:这些属性设置都是使用的 app 命名空间因为 ConstraintLayout 是像 Support libraries 也是作为库引入。它属于你的命名空间 app 而不是属于安卓框架(使用命名空间 android)。


删除约束

上面提到我们可以通过选中组件后出现的清空按钮来清除所有的约束。最后,我们还要介绍的是只删除其中一个约束。如果在 XML 源码中可以直接去掉相应的属性。若使用的是可视化编辑器,则通过点击约束锚点来去除约束条件。



Chain 链

参考文章:(https://constraintlayout.github.io/basics/create_chains.html) 作者:[Mark Allison]

什么是 Chain 链

Chain 链是一种特殊的约束让多个 chain 链连接的 Views 能够平分剩余空间位置。在 Android 传统布局特性里面最相似的应该是 LinearLayout 中的权重比 weight ,但 Chains 链能做到的远远不止权重比 weight 的功能。

开始创建 Chain 链

前面概要已经提到了 Chain 链是由多个 Views 组合的,所以要创建一个 Chain 链就需要先选择多个想要链接到一起的 Views ,然后再右键选择 'Center Horizontally' 或者 'Center Vertically' 来创建水平链或者垂直链。创建一个水平链,如下所示


首先,可以注意到 Chain 链两边末端的两个 View 已经存在了相对于父组件的左边缘和右边缘的约束。 Chain 链的创建定义的是 Chain 链组件之间的间隙关系,并不影响原有的非成员间的约束。如下刚刚创建后的图中,有很多视图上的标识符需要解释一下。如下所示:


观察截图,可以看到 Chain 链组件之间的连接类似于链条图案,而边缘两端的 View 与 父组件之间的连接类似于弹窗图案。最外面的连接图案代表了 Chain 链的链接模式(chain mode),链接模式决定了 Chain 链如何分配组件之间的剩余空间,你可以从 Chain 链每个组件下面的 “转换 Chain 模式” 按钮来切换 Chain 链模式。如下所示:


Chain 链模式一共有三种,分别为:spread ,spread_inside 和 packed 。

Spread Chain 链模式

Chain 链的默认模式就是 spread 模式,它将平分间隙让多个 Views 布局到剩余空间。如下所示:


Spread Inside Chain 链模式

Chain 链的另一个模式就是 spread inside 模式,它将会把两边最边缘的两个 View 到外向父组件边缘的距离去除,然后让剩余的 Views 在剩余的空间内平分间隙布局。如下所示:


Packed Chain 链模式

最后一种模式是 packed ,它将所有 Views 打包到一起不分配多余的间隙(当然不包括通过 margin 设置多个 Views 之间的间隙),然后将整个组件组在可用的剩余位置居中,如下所示:


在 packed chain 链模式,打包在一起的 Views 组可以进一步通过控制修改 bias 值来控制打包组的位置,在例子中 bias 模式是 0.5 将 Views 组居中。


Spread Chain 链的权重

spread 和 spread inside Chain 链可以设置每个组件的 weight 权重,这跟 LinearLayout 的 weight 权重设置很像。当前版本(Android Studio 3.0.1)的视图编辑器不能直接操作设置这个权重,不过我们可以通过属性视图(properties 视图)来手动设置属性。


对特定的组件设置 spread 权重,首先得选择这个 View 组件,假设该 View 是在一个水平的 Chain 链中,那么需要在属性视图(properties 视图)中设置 android:layout_width="0dp" 然后修改 app:layout_constraintHorizontal_weight="1",如下所示:


这时候观察 View 组件在 blueprint 蓝图视图模式中的改变,它的上边和下边缘都从直线变成了类似手风琴的线条,这符号就表示了 spread 或 spread inside Chain 链模式下的被设置了权重的组件。

同时要注意的是,在 packed Chain 链模式下设置权重 weight 并没有作用。就是说并不像 spread和 spread inside 模式中表现的占据尽可能的剩余空间,在 packed 模式下该组件就会被收缩成 0 大小。如下所示:


在 XML 中设置 Chain 链

虽然假如在 XML 中存在特有的属性设置 Chain 链模式会比较好,但事实上并没有特有的属性,而是现有的约束条件的一种组合。在 XML 中设置 Chain 链模式只需要设置好双向互补的约束。本文中首个例子的 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">

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="60dp"
        android:layout_marginTop="60dp"
        android:text="TextView"
        app:layout_constraintEnd_toStartOf="@+id/textView4"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="21dp"
        android:layout_marginTop="60dp"
        android:text="TextView"
        app:layout_constraintEnd_toStartOf="@+id/textView5"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/textView3"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="60dp"
        android:layout_marginTop="60dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/textView4"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

在 textView3  中设置了约束属性 app:layout_constraintEndToStartOf="@+id/textView4" ,而相对的在 textView4 也设置了约束属性 app:layout_constraintStart_toEndOf="@+id/textView3  " ,本质上就是创建两个约束条件,同一对锚点但是方向相反的约束条件,这就是 Chain 链的定义方式。

另外,textView3 中的约束属性 app:layout_constraintHorizontal_chainStyle="spread" 就是指定了链模式 spread 你可以通过修改成 spread inside 或 packed 来切换链模式,而且这个约束属性必须在链头,即是链组件中的第一个组件。

而设置链模式的 bias 可以通过设置约束属性 app:layout_constraintHorizontal_bias="0.75" 从 0.0 - 1.0 。

最后,我们就可以通过设置属性 android:layout_width="0dp" 以及 app:layout_constraintHorizontal_weight="1" 来设置 Chain 链中组件的权重。


参照线 guidelines

参考文章:(https://constraintlayout.github.io/basics/guidelines.html) 作者:[Mark Allison]


什么是参照线 guidelines

如果你熟悉 UI 设计软件你应该已经使用过参照线 guidelines 并对它的作用熟悉了。参照线 guideline 提供了视觉上的参照用于 Views 的对齐,而且不会在运行的时候显示,只要你熟悉它的使用了就会发现它对你的对齐实现非常方便。 Google 的 Material 设计原则推荐了使用 keylines 。该文章将介绍如何通过参照线 guidelines 来快速实现这些。

创建参照线 guidelines

创建垂直参照线 guidelines 需要在 blueprint 视图上右键打开上下文菜单,然后选择 Add Vertical Guideline 即可创建。如下所示:


当前版本的视图编辑器(Android Studio 3.0.1)默认隐藏参照线,选择 blueprint 内的 View 即可看到参照线。

参照线 guidelines 的类型

当前的参照线 guidelines 有三种类型,默认的第一种参考线是会有一个固定的偏移向父组件的 start 边缘(偏移量的单位是 dp)。本文开头创建的参照线对于父组件的 start 边缘参考线为 16dp。为了适配从右向左的布局设置,所以我们应该采用 start 边缘而不是 left 边缘。

第二种参考线则是有一个固定的偏移向父组件的 end 边缘。而最后一种参考线是根据父组件 ConstraintLayout 的宽度百分比来放置,而且参照线存在一个标识器,可以通过点击这个标识按钮来切换参考线的类型,如下所示:


向 start 和 end 类型的偏移量参照线非常适用于 keylines 的使用场景,而百分比形式的参照线则提供了类似于 PercentLayout 的一些功能。


调整参照线 guidelines

只要已经创建了参照线,我们可以通过拖动除类型标志器以外的地方的参照线来移动。如下所示:


你可以在例子中看到,对于一些特殊位置,如左右方向的 8dp 偏移量以及居中的 50% 位置,会对参照线有吸引力。

参照线 Guideline 实现原理

对于喜欢追根寻底的开发者,我们可以更深一步看看 Guideline 的内部实现。源码中 Guideline 类其实就是一个 View,而且它不会渲染任何东西因为它实现了一个 final 的 onDraw() 而且固定了它的可见性为 View.GONE ,这就决定了运行时不会显示任何东西,而在 View 的 layout 布局过程中它会占据一个位置,而其他组件可以通过它来布局对齐。所以实际上的 Guideline 只是一个极其轻量级没有任何显示但是可以用于约束布局对齐的 View 组件。

在 XML 中的 Guideline

我们可以看看一个 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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/relativeLayout2"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_marginStart="60dp"
        android:layout_marginTop="48dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="15dp"
        android:layout_marginStart="8dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        tools:layout_editor_absoluteY="92dp" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.13" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline11"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="20dp" />

</android.support.constraint.ConstraintLayout>

参照线 Guideline 拥有了一个属性 app:orientation="vertical" 来描述它是一个垂直的参照线。它还有属性 app:layout_constraintGuide_begin="16dp" 来描述它是一个对齐父组件的 start 边缘的 16dp 偏移量处。再次提醒的是,应该用 start 边缘而不是 left 边缘。当然切换向 end 类型的话,可以使用另一个属性 app:layout_constraintGuide_end="..." ,切换为百分比类型的参照线则是设置属性 app:layout_constraintGuide_percent="0.5" 值得取值范围为 0.0 到 1.0 ,描述的是百分比偏移量。

而此处的 TextView 源码则表现了,我们可以从 TextView 像对其他 View 一样对 Guideline添加约束向量,这样的原因就是刚刚分析的原理,因为 Guildeline 就是一个特殊的 View 。


ConstraintLayout基础系列之尺寸横纵比 dimensions

参考文章:(https://constraintlayout.github.io/basics/guidelines.html) 作者:[Mark Allison]

ConstraintLayout的尺寸 dimensions

有时候,我们需要创建一些固定方向比的 View 组件,最常使用固定横纵比的就是当 ImageView 用于展示一些固定横纵比的图片的时候。举些例子,书面封面(尺寸横纵比多种多样),电影海报(一般是 4:6 ),电影剧照(一般是 1.85:1 或 2.39:1 ),电视剧(一般是 4:3 或 16:9 )

对于不熟悉什么是横纵比的,横纵比就是表示了 View 的宽度与高度的比例 w:h 。例如,对于一个拥有横纵比为 4:6 拥有宽度为 40dp 的 View 组件有着高度是 60dp ,若它的宽度改为 30dp 则它的高度就是 45dp 。

若我们现实的图片能保证同样的横纵比和像素大小,我们可以简单的在两个方向上使用 wrap_content 即可。然而,现实情况由于数学四舍五入等多种原因都有可能造成实际现实的一些小误差。如果只是现实一个图片可能不会有多大问题,但是如果多个图片展示的时候小问题也会被有很不好的视觉效果,甚至当有 View 对齐于这些图片的 ImageView 的时候,也因此产生了变化,整体就会造成布局不平衡混乱了。

对于这个问题的解决方案之一是,通过创建继承于 ImageView 的子类,并通过覆写 onMeasure() 来实现固定横纵比的布局。常用的 support library 中的 PercentLayout 也提供了一些机制来结局这类横纵比问题。

同样的 ConstraintLayout 也提供了机制来专门解决这个问题,选择想要控制横纵比的 View 然后通过属性视图中修改 ratio 值来改变横纵比,红色圈内设置,如下所示


如上图,我们设置的 View 组件有着向父组件的 start 和 top 边缘的约束,它的 end 边缘则约束向一条参考线,而 bottom 边缘则没有被约束,这个 View 的 layout_width 和 layout_height 都被设置成 match_constraint,表示他们会根据所有的约束来设置宽高。在布局阶段这个组件的宽度就被计算好了,但是它的高度好像没有被确定。然后,因为设置了宽高横纵比,高度其实也被确定了,只是宽度的一个函数输出值(在以上例子中横纵比是 16:9 )

这样设置的好处就是,当宽度变化的时候,高度自动跟着变化,如下图通过移动这个 View 组件 end 边缘约束向的参照线就可以看到效果。如下所示:


在 XML 中的尺寸横纵比 DimensionRatio

上例中的 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">

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="36dp"
        android:layout_marginTop="24dp"
        app:layout_constraintDimensionRatio="h,16:9"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_end="264dp" />
</android.support.constraint.ConstraintLayout>

可以发现,设置横纵比的属性是 app:layout_constraintDimensionRatio ,而这个值有两个部分组成:方向和比例值。

通过上面的视图编辑器,我们已经知道了宽度就是输入的固定值,从而设置了方向是 h 标识了 horizontal 。其实这个方向可以不用设置,在运行时的 layout 布局过程就可以计算推断出来,但显示的在 xml 源码中声明避免了所有可能出现模棱两可的情况发生。在大多数情况下,这非常不必要因为本身方向是不言自明的,就像例子中,唯有高度没被约束,很容易推断出来高度是根据宽度来的变量函数。

这种横纵比的组件往往又很大的说服力,当横纵比的权利被赋予的时候。

最后还要提到的是,上文提到的宽高属性被设置成 match_constraint 实际上在 XML 源码中表现是被设置成 0dp,这就像 LinearLayout 的 weight 属性一样,会在 XML 中设置为 0dp ,而实际大小会根据父组件在布局 layout 过程中的大小来决定计算出来。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章