Android ConstraintLayout用法全解析

ConstraintLayout 用法全解析


前言

自学习ConstraintLayout以来,对他是又爱又恨,慢慢整理一下用法,希望可以帮到大家。

长文章预警-------------


一、 什么是ConstraintLayout?

ConstraintLayout约束布局,是在 2016 年 Google I/O上发布的布局,
其目的是为了解决复杂的页面层级嵌套过多的问题。

引入:
implementation ‘androidx.constraintlayout:constraintlayout:2.0.4’

二、 ConstraintLayout的优缺点?

2.1 优点

减少布局嵌套,有较高的性能优势。众所周知,Android 绘制过程包括三个阶段,一是测量,由根布局开始向下遍历视图树,View会测量自己的大小,ViewGroup则会测量全部的子View,最终确定布局中所有ViewGroup和View的大小;二是布局,测量阶段我们知道了布局中所有组件的大小,我们就可以按照规则摆放他们,由根布局开始向下遍历视图树,确认位置;三是绘制,经历测量和布局阶段,我们知道了组件的大小和摆放位置,剩下的就是绘制到屏幕展现给用户,依旧是由根布局开始向下遍历视图树,执行绘制。

整个过程中我们需要多次的执行遍历操作。因此,我们布局中的嵌套层级越多,整个过程所需的时间和计算也就越多。当我们使用ConstraintLayout进行布局时,只要我们想,完全可以只用ConstraintLayout一层就能完成,所以性能会好很多。

2.1 缺点

组件之间相互约束、相互关联,在使用不当的情况下去,很容易让人陷入约束地狱。

三、 基本使用方法

3.1 最基础使用 — 位置约束

ConstraintLayout 可以理解为超级加倍版的RelativeLayout,我们在使用RelativeLayout时可以通过layout_alignParentTop、layout_alignParentBottom、layout_alignParentLeft、layout_alignParentRight 确定子组件相对于父布局的位置,可以通过layout_above、layout_below、layout_toLeftOf、layout_toRightOf确认子组件相对于另一个兄弟组件的位置。这就是位置约束,子组件的位置收到父布局和兄弟组件位置的约束,ConstraintLayout进一步加强和优化了这种约束。

ConstraintLayout的位置约束属性如下,属性值可以是parent或者@+id/兄弟组件的id:
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

属性解析:
1.可以看到这些属性的格式都是layout_constraintXXX_toYYYOf,我们知道在Android中组件都是矩形的,矩形都会有四条边,上Top 下 Bottom 左Left(Start)右Right(End),其中XXX代表当前的组件的某一条边,YYY代表约束XXX的parent或者兄弟组件的某一条边,并且当XXX和YYY代表的边相同时,表示两条边对齐,并且当XXX和YYY代表的边相反时,表示一条边在另一条边的一侧。
例如:
layout_constraintLeft_toLeftOf=“parent” 代表当前组件的左边和父组件的左边对齐。
layout_constraintLeft_toRightOf="@+id/tvUserName" 代表当前组件的左边在id是tvUserName的兄弟组件的右边,也就是当前组件在tvUserName 的右边。

2.竖直方向(Top、Bottom)和水平方向(Left、Right)必须完成约束,因为要确定一个组件的位置,我们必须知道他水平方向的位置和竖直方向的位置。

示例如下:tvUserName通过layout_constraintLeft_toLeftOf=“parent” 让自己的左边和父布局的左边对齐,layout_constraintTop_toTopOf="parent"让自己的上边和父布局的上边对齐,水平和竖直方向的约束都确定了。
tvUserSex 通过layout_constraintLeft_toRightOf让自己的左边在tvUserName的右侧,layout_constraintTop_toTopOf="parent"让自己的上边和父布局的上边对齐,水平和竖直方向的约束都确定了。如果我们把tvUserSex的两个属性去掉一个,那么tvUserSex在某一个方向上就不知道该把自己放在哪,就会报错。

<androidx.constraintlayout.widget.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:background="@color/white">

    <TextView
        android:id="@+id/tvUserName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/purple_200"
        android:padding="10dp"
        android:text="用户名称"
        android:textColor="@color/white"
        
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </TextView>

    <TextView
        android:id="@+id/tvUserSex"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/purple_500"
        android:padding="10dp"
        android:text="男"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toRightOf="@+id/tvUserName"
        app:layout_constraintTop_toTopOf="parent">

    </TextView>

</androidx.constraintlayout.widget.ConstraintLayout>

效果如下:

3.2 文字基线对齐

基线:我们小时候写英文单词时用的四线格,基线大致就可以理解为四线格的第三条线。
layout_constraintBaseline_toBaselineOf可以让两个文本的基线对齐

我们继续用3.1 的代码,改一下tvUserSex的文字大小,去掉padding,增加
app:layout_constraintBaseline_toBaselineOf="@+id/tvUserName"让
tvUserSex和tvUserName基线对齐:

    <TextView
        android:id="@+id/tvUserName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/purple_200"
        android:text="用户名称"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </TextView>

    <TextView
        android:id="@+id/tvUserSex"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/purple_500"
        android:text="男"
        android:textColor="@color/white"
        android:textSize="12sp"
        app:layout_constraintBaseline_toBaselineOf="@+id/tvUserName"
        app:layout_constraintLeft_toRightOf="@+id/tvUserName"
        app:layout_constraintTop_toTopOf="parent">

    </TextView>

效果如下:

3.3 居中和bias百分比偏移

1.父布局水平居中:
如果设置app:layout_constraintLeft_toLeftOf=“parent”,则view会贴着父view的左边,设置app:layout_constraintRight_toRightOf="parent"则会贴着右边,那如果两个都设置那么当前组件就会在父布局中水平居中
示例如下:

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_200"
    android:text="用户名称"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

效果如下:

2.父布局竖直居中:同理,如果同时设置app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintTop_toTopOf="parent"那么当前组件就会在父布局中竖直居中

3.父布局水平和竖直同时居中:同时设置
app:layout_constraintLeft_toLeftOf=“parent”
app:layout_constraintRight_toRightOf=“parent”
app:layout_constraintTop_toTopOf=“parent”
app:layout_constraintBottom_toBottomOf=“parent”

4.约束范围内居中:从上面可以看出,如果我们给当前组件两边(水平方向或竖直方向)都加一个约束条件,在无法确定倾向哪边的情况下,组件就会居中。所以上面三个居中同样可以把属性值parent换成兄弟组件的id,那么当前组件在约束范围内就会居中显示。

5.百分比偏移: 如果我想让当前组件向左偏一些,比如位于父布局的1/3处该怎么办?这时候就需要使用如下属性
layout_constraintHorizontal_bias
属性值偏移量,取值范围从0~1,0即挨着左边,1是挨着右边,所以要让当前组件位于父布局的1/3处,应该设置
app:layout_constraintHorizontal_bias=“0.3”,效果图如下:

事实上,bias值=当前View左约束的长度/(当前View左约束的长度+当前VIew右约束的长度),默认值为0.5(所以默认居中)
当我们设置app:layout_constraintHorizontal_bias=“1”,此时左约束长度占据了所有的约束长度,所以效果如下:

3.4 角度约束

ConstraintLayout可以让我们轻松的实现一个控件在另一个控件的某个角度的某个位置。
你需要用到下面这些属性
layout_constraintCircle 角度中心组件的id
layout_constraintCircleAngle 角度(0-360)
layout_constraintCircleRadius 到中心的距离

示例如下:tvUserSex在tvUserName的45度方向,距离为100dp.

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="100dp"
    android:background="@color/purple_200"
    android:text="用户名称"
    android:textColor="@color/white"

    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

<TextView
    android:id="@+id/tvUserSex"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp"
    android:background="@color/purple_200"
    app:layout_constraintCircle="@id/tvUserName"
    app:layout_constraintCircleAngle="45"
    app:layout_constraintCircleRadius="100dp"
    >

</TextView>

效果如下:

3.5 margin 和 goneMargin

ConstraintLayout 中子组件的使用和普通布局是一样的,唯一有区别的地方是,必须存在约束margin才会生效:
android:layout_margin
android:layout_marginStart
android:layout_marginLeft
android:layout_marginTop
android:layout_marginEnd
android:layout_marginRight
android:layout_marginBottom
除了普通的margin外,还有goneMargin,当 当前组件对应边的约束组件隐藏时会生效,显示时不生效。

如下:三个文本框相互约束水平并排,我们给tvUserSex设置了15dp的layout_goneMarginLeft,当tvUserSex的左边约束组件tvUserName显示的时候layout_goneMarginLeft并没有起作用,我们把tvUserName设置为gone,我们看到tvUserSex 的layout_goneMarginLeft显示了出来。

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_200"
    android:padding="10dp"
    android:text="用户名称"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

<TextView
    android:id="@+id/tvUserSex"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_500"
    android:text="男"
    android:textColor="@color/white"
    android:padding="10dp"
    android:textSize="18sp"
    app:layout_goneMarginLeft="15dp"
    app:layout_constraintLeft_toRightOf="@+id/tvUserName"
    app:layout_constraintTop_toTopOf="parent">

</TextView>
<TextView
    android:id="@+id/tvUserAge"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_700"
    android:text="今年18"
    android:textColor="@color/white"
    android:padding="10dp"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@+id/tvUserSex"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

效果如下:

3.6 0dp(MATCH_CONSTRAINT)与组件大小

设置view的大小我们可以使用wrap_content、指定尺寸、match_parent,ConstraintLayout还可以设置为0dp(MATCH_CONSTRAINT),
odp并不会使组件大小变为0,而是会因为不同的属性产生不同的效果
,属性为:。
layout_constraintWidth_default
Layout_constraintHeight_default
要想使这两个属性生效,我们需要设置组件的layout_width=“0dp”或者layout_height=“0dp”
属性的取值可以是:
spread(默认值,占据所有可以占据的空间)
percent(父布局的百分比,代表是当前组件的大小是父布局大小的一定比例,需要配合layout_constraintWidth_percent使用,取值是0-1的小数,当设置layout_constraintWidth_percent后,layout_constraintWidth_default="percent"会被默认,即使我们已经设置了spread或者wrap)
wrap(大小自适应)

如下:文本框tvUserName左边和父布局左边对齐,右边和父布局右边对齐,那么水平方向上,tvUserName可以占据整个父布局的大小。当我们设置layout_width=“0dp”,layout_constraintWidth_default默认是spread,会让tvUserName占据所有的约束空间,实例如下

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="@color/purple_200"
    android:padding="10dp"
    android:text="用户名称"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

效果如下:

如果我们设置layout_constraintWidth_default为wrap,大小自适应,效果如下:

如果我们设置layout_constraintWidth_default为percent,大小为父布局的百分比,当我们设置为0.5即大小为父布局的二分之一后,示例如下:

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="@color/purple_200"
    android:padding="10dp"
    android:text="用户名称"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_default="wrap"
    app:layout_constraintWidth_percent="0.5">

</TextView>

3.7 0dp与宽高比例

ConstraintLayout中可以很简单的对宽高设置比例,使用的属性为layout_constraintDimensionRatio

1.要使用layout_constraintDimensionRatio属性要求layout_width、layout_height至少有一个设置为0dp。
2. 建议layout_width、layout_height至少有一个设置为0dp ,另外一个属性是具有明确的大小,包括match_parent、具体的尺寸值、0dp。这是因为我们有了宽高之间的比例,我们需要一个明确的宽度值或者高度值,去计算另一个值,如果我们设置成为wrap_content,会出现很多异常的情况,这是我们不愿看到的。
3. layout_constraintDimensionRatio属性的类型是一个字符串,属性值是
radio比例,就是m;n形式的字符串,是宽高比或者高宽比的意思。
除了直接写radio比例以外,我们还可以写成 ,前缀wh对我们计算会产生影响。
w,radio比例。
h,radio比例。

验证:
1.设置一个文本框如下属性
android:layout_width=“0dp”
android:layout_height=“100dp”
app:layout_constraintDimensionRatio=“w,2:1”
此时宽度是高度的2倍, 当我们直接设置2:1 时也是这样,效果如下:

2. 设置一个文本框如下属性
android:layout_width=“0dp”
android:layout_height=“100dp”
app:layout_constraintDimensionRatio=“h,2:1”
此时宽度是高度的一半,效果如下:

3.设置一个文本框如下属性
android:layout_width=“100dp”
android:layout_height=“0dp”
app:layout_constraintDimensionRatio=“h,2:1”
此时高度是宽度的一半, 当我们直接设置2:1 时也是这样,效果如下:

4. 设置一个文本框如下属性
android:layout_width=“100dp”
android:layout_height=“0dp”
app:layout_constraintDimensionRatio=“w,2:1”
此时高度是宽度的两倍,效果如下:

5 . 设置一个文本框如下属性
android:layout_width=“0dp”
android:layout_height=“0dp”
app:layout_constraintDimensionRatio=“h,2:1”
此时高度是宽度的一半,当我们直接设置2:1 时也是这样

6. 设置一个文本框如下属性
android:layout_width=“0dp”
android:layout_height=“0dp”
app:layout_constraintDimensionRatio=“w,2:1”
此时高度是宽度的两倍,效果如下:

如果我们不使用ConstraintLayout,我们为了实现宽高比例,往往只能通过自定义组件实现,如下,这是一个比较经典的宽高一比一的RelativeLayout:

public class SquareRelativeLayout  extends RelativeLayout {

    public SquareRelativeLayout(Context context) {
        super(context);
    }

    public SquareRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Set a square layout.
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }

}


3. 8 Chains(约束链)

在水平或者垂直方向的全部控件,相邻的组件两两之间首尾互相约束(任何一个组件都要有两边的约束,少了一个都不能形成链),这样就可以形成一条链。

我们可以使用layout_constraintHorizontal_chainStyle、layout_constraintVertical_chainStyle分别对水平和垂直链设置模式,
模式值有:
spread(默认、所有的组件均分剩余空间)
Packed(所有组件贴紧居中)
spread_inside(两侧的控件在两边,中间的组件均分剩余空间)

layout_constraintHorizontal_chainStyle和layout_constraintVertical_chainStyle只有对水平链或者竖直链上的第一个组件设置有效。

如下:水平排列的三个文本框两两之间相互约束形成了一条水平链,其中
tvUserName使用 app:layout_constraintLeft_toLeftOf=“parent” 使左边对齐父布局的左边,还使用了 app:layout_constraintRight_toLeftOf="@+id/tvUserSex",尽管我们不设置该属性,通过tvUserSex 设置app:layout_constraintLeft_toRightOf="@+id/tvUserName"也能使tvUserSex在tvUserName的右边,但如果我们不设置,那么tvUserName的右边就没有了约束,没有和相邻的组件收尾相连,就无法形成链,所以这个设置是必须的。

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_200"
    android:padding="10dp"
    android:text="用户名称"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/tvUserSex"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

<TextView
    android:id="@+id/tvUserSex"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_500"
    android:padding="10dp"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@+id/tvUserName"
    app:layout_constraintRight_toLeftOf="@+id/tvUserAge"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

<TextView
    android:id="@+id/tvUserAge"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/purple_700"
    android:padding="10dp"
    android:text="今年18"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@+id/tvUserSex"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</TextView>


如果我们设置app:layout_constraintHorizontal_chainStyle=“packed”,效果如下:

如果我们设置app:layout_constraintHorizontal_chainStyle=“spread_inside”,效果如下:

layout_constraintHorizontal_weight和layout_constraintHorizontal_weight 对链的影响:

当一条链中的组件使用layout_constraintWidth_default=“spread”,我们可以使用layout_constraintHorizontal_weight 和layout_constraintHorizontal_weight来设置组件在链中的权重,也就是占比。
要注意的是,如果设置的这两个属性,链的组件将会占满整条链的空间,weight和percent 是不同的。示例如下:

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="@color/purple_200"
    android:text="用户名称"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintWidth_default="spread"
    app:layout_constraintHorizontal_weight="0.2"
    app:layout_constraintVertical_weight=""
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/tvUserSex"
    app:layout_constraintTop_toTopOf="parent">

</TextView>
<TextView
    android:id="@+id/tvUserSex"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="@color/purple_500"
    android:text="男"
    app:layout_constraintHorizontal_weight="0.8"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintWidth_default="spread"
    app:layout_constraintLeft_toRightOf="@id/tvUserName"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</TextView>

效果如下:

3.9 Guideline(参考线)

Guideline是参考线,可以帮助开发者进行辅助定位,像是数学几何中的辅助线一样并且实际上它并不会真正显示在布局中,可以用来做一些百分比分割之类的需求
android:orientation=“horizontal|vertical” 辅助线的对齐方式
app:layout_constraintGuide_percent=“0-1” 距离父布局宽度或高度的百分比(取值范围0-1)

如下:我们在屏幕正中间加了一条辅助线,然后我们在辅助线下边添加了一个文本框,这比传统的布局的居中效果更容易控制。
示例代码:

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    app:layout_constraintGuide_begin="100dp"
    app:layout_constraintGuide_percent="0.5" />


<TextView
    android:id="@+id/tvSecond"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@+id/guideline">

</TextView>

效果如下:

3.10 Barrier(屏障)

和Guideline一样,也不会实际出现在布局中,作用是分隔布局。
想象一个场景:竖着两个文本框12,文本框12的长度未知,与两个文本框水平并行一个文本框3,文本框3要在文本框12的右边,此时正常的位置约束就有些尴尬。

它有两个属性:
app:constraint_referenced_ids=“id,id”
一组控件的id,id之间用逗号分隔,Barrier将会使用id中最大的一个的宽/高作为自己的位置
app:barrierDirection=“top|bottom|left|right|start|end”
用于控制Barrier相对于给定id的组件的位置
示例代码如下:

<TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="@color/white"
    android:textSize="18sp"
    android:text="数据1数据"
    android:background="@color/purple_200"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="@color/white"
    android:textSize="18sp"
    android:background="@color/purple_500"
    android:text="数据2数据2数据2"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/textView1" />

<androidx.constraintlayout.widget.Barrier
    android:id="@+id/barrier"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="textView2,textView1" />

<TextView
    android:id="@+id/textView3"
    android:layout_width="0dp"
    android:background="@color/purple_700"
    android:layout_height="wrap_content"
    android:text="数据3数据3数据3数据3数据3数据3数据3数据3数据3数据3数据3"
    app:layout_constraintLeft_toRightOf="@+id/barrier"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

效果如下:

3.11 Placeholder(占位符)

Placeholder可以在布局中事先占据一个位置,在代码中使用Placeholder.setContentId(布局id),就可以让某个控件移动到此占位符中。
示例代码如下,效果可自行尝试:

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    android:text="用户姓名"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Placeholder
    android:layout_width="100dp"
    android:id="@+id/placeholder"
    android:layout_height="100dp"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

3.12 Group(组)

我们常常会有同时隐藏或者显示多个控件的需求,最常用的方法就是把这些组件放到一个布局中对这个布局进行显示或隐藏,或者一个个的设置显示隐藏。
Group的作用就是可以对一组控件同时隐藏或显示,没有其他的作用,属性如下:
app:constraint_referenced_ids=“id,id” 加入组的控件id,id之间用逗号隔开。然后在代码中使用Group.setVisibility();就可以控制所有组件的显示与隐藏。
示例代码如下,效果自行尝试:

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    android:text="用户姓名"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />


<TextView
    android:id="@+id/tvUserSex"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_500"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@id/tvUserName"
    app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Group
    android:layout_width="wrap_content"
    android:id="@+id/group"
    app:constraint_referenced_ids="tvUserName,tvUserSex"
    android:layout_height="wrap_content">

</androidx.constraintlayout.widget.Group>

3.13 Flow流式布局

流式布局使我们经常用到的布局,广泛的用在列表(尤其是图片列表)、标签、分类展示等场景中,传统的流式布局 我们可以使用 RecyclerView重写LayoutManager实现、也可以使用Google比较经典的流式布局FlexboxLayout
实现,功能都很强大。

ConstraintLayout也提供了流式布局的实现。
Flow组件,他同样有一个constraint_referenced_ids属性,属性值也是组件的id,id之间用逗号分割,表示纳入流式布局的管理。
控制流式布局的样式:
flow_wrapMode设置排列方式
none(默认值):所有的组件并排,超出屏幕两侧的组件不可见
示例代码如下:

<TextView
    android:id="@+id/tvA"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    android:text="用户姓名"
    android:textColor="@color/white"
    android:textSize="18sp" />


<TextView
    android:id="@+id/tvB"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_500"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp" />

<TextView
    android:id="@+id/tvC"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    android:text="用户姓名"
    android:textColor="@color/white"
    android:textSize="18sp" />


<TextView
    android:id="@+id/tvD"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_500"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp" />
<TextView
    android:id="@+id/tvE"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    android:text="用户姓名"
    android:textColor="@color/white"
    android:textSize="18sp" />


<TextView
    android:id="@+id/tvF"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_500"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp" />
<androidx.constraintlayout.helper.widget.Flow
    android:id="@+id/group"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="tvA,tvB,tvC,tvD,tvE,tvF"

    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</androidx.constraintlayout.helper.widget.Flow>

效果如下:

chian:超出范围的组件会自动换行,不满一行的组件会平分宽度。

aligned:超出范围的组件会自动换行, 换行后从头排列。
其他的属性flow_horizontalGap 水平间隔flow_verticalGap 竖直间隔
上面的示例使用aligned并且增加app:flow_horizontalGap="2dp"app:flow_verticalGap=“5dp” 后效果:

当flow_wrapMode=“chain” 的时候,我们可以设置样式,样式和我们前面讲到的链是一样的。
app:flow_horizontalStyle=“packed|spread|spread_inside” 所有水平链的配置
app:flow_verticalStyle=“packed|spread|spread_inside” 所有垂直链的配置

app:flow_firstHorizontalStyle=“packed|spread|spread_inside” 第一条水平链的配置,其他条不生效
app:flow_firstVerticalStyle=“packed|spread|spread_inside” 第一条垂直链的配置,其他条不生效
app:flow_lastHorizontalStyle=“packed|spread|spread_inside” 最后一条水平链的配置,其他条不生效
app:flow_lastVerticalStyle=“packed|spread|spread_inside” 最后一条垂直链的配置,其他条不生效

我们分别添加app:flow_horizontalStyle=“packed|spread|spread_inside” 效果如下:



flow_maxElementsWrap可以设置每行可布局组件最大的数量,当我们设置
app:flow_maxElementsWrap="3"的时候可以看到:

3.14 Layer(层布局)

Layer也拥有一个属性constraint_referenced_ids,属性值也是id,id之间用逗号隔开,Layer的大小就是所包含的所有组件的范围,Layer的主要作用就是设置共同的一些东西,比如设置一个背景。
如果代码Layer放在所有引用的组件的最后面,那么它就会在所有引用组建的的最上面,如果此时添加背景,就会把引用的组件覆盖掉,反之则是最下面,
示例代码如下:

<androidx.constraintlayout.helper.widget.Layer
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@mipmap/ic_launcher_round"
    app:constraint_referenced_ids="tvA,tvB"
    tools:ignore="MissingConstraints">

</androidx.constraintlayout.helper.widget.Layer>

<TextView
    android:id="@+id/tvA"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/purple_200"
    android:text="用户姓名"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<TextView
    android:id="@+id/tvB"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:background="@color/purple_500"
    android:text="男"
    android:textColor="@color/white"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"

    app:layout_constraintTop_toTopOf="parent" />

效果如下(xml中放置的位置不同):


四、 特定场景的优秀使用

4.1 .解决两个组件并排第一个组件过长把第二个组件挤没了的问题(实现两个组件并排,第一个组件宽度自适应,第二个组件跟在第一个组件后面,并且当第一个组件过长时第二个组件不会被挤没)

这种效果非常常见,使用普通的布局很难做到这种效果,而使用ConstraintLayout会非常的简单。
第一个 组件 设置如下:宽度自适应限制区域并且不会占满限制区域,然后设置链为packed,并且左靠边占据约束空间。
android:layout_width=“0dp”
app:layout_constraintWidth_default=“wrap”
app:layout_constraintHorizontal_chainStyle=“packed”
app:layout_constraintHorizontal_bias=“0”
示例代码如下:

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:textColor="@color/white"
    android:textSize="16sp"
    android:text="这是一条短文本"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHorizontal_bias="0"
    app:layout_constraintHorizontal_chainStyle="packed"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/ll_phone"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_default="wrap">

</TextView>


<LinearLayout
    android:id="@+id/ll_phone"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"

    android:gravity="center_horizontal"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toRightOf="@+id/tvUserName"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <ImageView
        android:layout_width="19dp"
        android:layout_height="19dp"
        android:layout_marginLeft="15dp"
        android:src="@mipmap/ic_launcher"></ImageView>
</LinearLayout>

效果如下:
当文本很短的时候:

当文本很长的时候:

4.2 N等分+固定比例布局

有时候我们希望实现这种布局,比如比赛、积分排名等。
宽高比例肯定用到layout_constraintDimensionRatio,n等分我们用到的是
layout_constraintWidth_percent和链实现。

示例代码如下:

<TextView
    android:id="@+id/tvSecond"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:background="@color/purple_200"
    app:layout_constraintDimensionRatio="1:2"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/tvFirst"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_percent="0.3">

</TextView>

<TextView
    android:id="@+id/tvFirst"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:background="@color/purple_500"
    app:layout_constraintDimensionRatio="1:2"
    android:layout_marginTop="30dp"
    app:layout_constraintLeft_toRightOf="@id/tvSecond"
    app:layout_constraintRight_toLeftOf="@+id/tvthird"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_percent="0.3">

</TextView>

<TextView
    android:id="@+id/tvthird"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:background="@color/purple_700"
    app:layout_constraintDimensionRatio="1:2"
    app:layout_constraintLeft_toRightOf="@id/tvFirst"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_percent="0.3">

</TextView>

五、 MotionLayout 基础入门

1.MotionLayout是constraintlayout2.0以后新增加的类,如果想用使用需要constraintlayout的版本号大于2.0。
2.MotionLayout是constraintlayout的子类,要想使用我们可以把布局中的constraintlayout替换成MotionLayout即可,MotionLayout增加了管理布局中组件的运动轨迹和动画的功能,可以让布局转换和复杂的布局动画更加简单。
3.使用MotionLayout除了正常的布局xml文件以外,我们还需要在res/xml/文件夹增加一个xml文件,用来描述动画场景,该文件需要与正常的布局xml文件相对应,最后需要将该文件赋值给MotionLayout的layoutDescription属性
4.
4.1 动画描述文件:根节点是MotionScene
然后可以包含一个Transition节点和两个ConstraintSet节点

4.2 Transition节点代表一个动画转换,其中constraintSetStart代表动画开始的状态,constraintSetEnd代表动画结束的状态,属性值都是一个ConstraintSet节点的id,duration代表动画的时长。

4.3 Transition节点还可以包含OnClick节点,OnClick节点代表可以通过点击事件触发动画,其中targetId属性值是组件id,代表点击该组件出发动画,clickAction 代表动画动作,取值如下:
transitionToStart 过渡到 constraintSetStart 属性指定的状态,有过渡动画效果
transitionToEnd过渡到 constraintSetEnd 属性指定的状态,有过渡动画效果
jumpToStart
直接跳转到 constraintSetStart 属性指定的状态,没有动画效果
jumpToEnd
直接跳转到constraintSetEnd 属性指定的状态。
toggle
默认值。在constraintSetStart和 motion:constraintSetEnd 指定的状态之间切换,如果处于start状态就过渡到end状态,如果处于end状态就过渡到start状态,有过渡动画。

4.4 Transition节点还可以包含OnSwipe节点,OnSwipe节点代表可以通过滑动事件触发动画,其中touchAnchorId属性代表要拖动的组件id,touchAnchorSide属性要拖动的组件哪一条边dragDirection表示用户滑动的方向

4.5 Transition节点还可以包含KeyFrameSet节点,
默认情况下,我们只需要定义动画开始和结束状态,MotionLayout 会帮我们平滑的过度。如果我们想要整个动画过程由我们自己控制,就需要KeyFrameSet来设置。
KeyFrameSet节点可以包含KeyPosition、KeyAttribute、KeyCycle、KeyTimeCycle、KeyTrigger节点。

4.5.1 KeyPosition节点可以改变动画过程中组件的位置,也就是我们可以做曲线运动,而不是简单的直线运动。
KeyPosition 属性:
framePosition表示动画的进度,取值范围为[0,100]。写50就表示动画进度执行50%的地方。
motionTarget表示作用组件的id
keyPositionType 表示坐标系类型,取值可以是parentRelative(表示以MotionLayout 布局为参考系,布局左上角为(0,0),右下角为(1,1)、pathRelative(视图起始点(0,0),视图的终点坐标是(1,0))、deltaRelative(视图的起始点坐标为(0,0), 终点坐标为(1,1))
percentY 和 motion:percentX 就是相对参考系的纵向和横向的比例。
系统会根据我们的设置规划相应的曲线。
动画起点、动画终点、和MotionLayout 布局坐标系(0.8,0.5)的三个点规划了一条曲线。

<KeyPosition
    motion:framePosition="50"
    motion:keyPositionType="parentRelative"
    motion:motionTarget="@id/tvUserName"
    motion:percentX="0.8"
    motion:percentY="0.5"></KeyPosition>

4.5.2 KeyAttribute
keyAttribute 可以改变在动画的过程中某个时刻的属性
同样需要两个属性:framePosition motionTarget,意义同上。
其他的属性都是View 原生属性,比如我们在 节点中罗列的那些。
在动画到50%的时候透明度变为0

<KeyAttribute
    android:alpha="0"
    motion:framePosition="50"
    motion:motionTarget="@id/tvUserName">

</KeyAttribute>

4.5.3 KeyCycle 循环动画
在一定范围内循环动画。
framePosition 表示作用范围,比如50代表作用到动画运行的50%
motionTarget 表示作用的组件id
wavePeriod 表示运动的周期数
motion:waveShape 表示周期的类型,取值有:sin、square、triangle
sawtooth、reverseSawtooth、cos、bounce

<KeyCycle
    android:translationY="100dp"
    motion:framePosition="100"
    motion:motionTarget="@+id/tvUserSex"
    motion:wavePeriod="1"
    motion:waveShape="sin" />

4.5.4 KeyTimeCycle 循环动画
KeyTimeCycle在帧上做周期性,KeyCycle是在动画过程中做周期性
属性一样

<KeyTimeCycle
    android:translationY="100dp"
    motion:motionTarget="@+id/tvUserSex"
    motion:waveShape="sin"
    motion:wavePeriod="1" />

4.5.5 KeyTrigger
在动画的过程中可以触发定义的函数。
framePosition 动画的进度,
motionTarget 作用的目标,
onCross要触发的函数名,动画不管是正向还是反向都会触发
onPositiveCross 要触发的函数名,只有正向执行动画是到达设置的进度才会触发
onNegativeCros 要触发的函数名,,只有反向执行动画是到达设置的进度才会触发

4.6 节点代表动画开始结束时的组件的状态,属性值包括组件id、组件的动画属性、组件的布局约束。
其中id属性就是对应xml布局中的某一个组件的id,布局约束的属性就是正常的ConstraintLayout的属性,用来约束动画开始和动画结束时组件的位置,
我们还可以设置其他的属性来丰富动画,这里简单罗列一些:
大小
位置
边界
alpha
visibility
Elevation
scaleX、scaleY
rotation、rotationX、rotationY
translationX、translationY、translationZ

一个简单ConstraintSet节点的示例如下:

<Constraint
    android:id="@+id/tvUserSex"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:alpha="1"
    motion:layout_constraintStart_toStartOf="@+id/tvUserName"
    motion:layout_constraintTop_toBottomOf="@+id/tvUserName" />

一个整的示例文件如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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:background="@color/teal_200"
    app:layoutDescription="@xml/exmple">

    <TextView
        android:id="@+id/tvUserName"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/purple_200"
        android:text="用户姓名"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent" />

    <TextView
        android:id="@+id/tvUserSex"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/purple_500"
        android:text="男"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.motion.widget.MotionLayout>


<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@+id/tvUserName">

        </OnClick>

        <!--        <OnSwipe-->
        <!--            motion:dragDirection="dragRight"-->
        <!--            motion:touchAnchorId="@id/tvUserName"-->
        <!--            motion:touchAnchorSide="left">-->

        <!--        </OnSwipe>-->
        <KeyFrameSet>
            <KeyPosition
                motion:framePosition="50"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/tvUserName"
                motion:percentX="0.8"
                motion:percentY="0.5"></KeyPosition>

            <KeyAttribute
                android:alpha="0"
                motion:framePosition="50"
                motion:motionTarget="@id/tvUserName">

            </KeyAttribute>

            <KeyCycle
                android:translationY="100dp"
                motion:framePosition="100"
                motion:motionTarget="@+id/tvUserSex"
                motion:wavePeriod="1"
                motion:waveShape="sin" />


        </KeyFrameSet>

    </Transition>
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/tvUserName"
            android:layout_width="100dp"
            android:layout_height="100dp"

            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintStart_toStartOf="parent" />
        <Constraint
            android:id="@+id/tvUserSex"
            android:layout_width="100dp"
            android:layout_height="100dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/tvUserName"
            android:layout_width="100dp"
            android:layout_height="100dp"

            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />

        <Constraint
            android:id="@+id/tvUserSex"
            android:layout_width="100dp"
            android:layout_height="100dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

</MotionScene>


ConstraintLayout的用法实在太多,尤其是2.0以后,这里暂时整理这么多,后续慢慢补充。


学习永远不会太迟。 该文章持续更新修改补充,如有错误,欢迎评论指正。
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值