学习扔物线进阶视频课程笔记。ConstraintLayout使用

使用ContraintLayout,可以灵活地实现复杂UI,同时可以减少布局嵌套,提升UI性能(层级过深会增加绘制界面时间,影响用户体验)

ConstraintLayout中的子view,至少需要添加两个约束,一个垂直方向,一个水平方向

使用start和end来代替left和right,可以很好地适配中东地区的从右到左布局(阿拉伯语语言是从右到左RTL)。

以下示例,ConstraintLayout作为父容器,白色底,宽高300dp

布局:

居中父容器:

效果图:
代码:

      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"

居中于控件:

效果图:
代码:

      app:layout_constraintStart_toStartOf="@id/view1"
      app:layout_constraintEnd_toEndOf="@id/view1"
      app:layout_constraintTop_toBottomOf="@id/view1"
      app:layout_constraintBottom_toBottomOf="@id/view1"

填充:

效果图:
使用0dp(match_constraint)或者match_parent
代码:

    <View
      android:id="@+id/view1"
      android:layout_width="match_parent"  //或者0dp
      android:layout_height="100dp"
      android:background="@color/teal_200"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

权重:

效果图:
代码:

      //v1
      android:layout_width="0dp"
      app:layout_constraintHorizontal_weight="1"

      //v2
      android:layout_width="0dp"
      app:layout_constraintHorizontal_weight="2"

      //v3
      android:layout_width="0dp"
      app:layout_constraintHorizontal_weight="1"

文字基准线对齐:

效果图:
代码:

      app:layout_constraintBaseline_toBaselineOf="@id/tv1"
      app:layout_constraintStart_toEndOf="@id/tv1" 

圆形定位

通过【圆心】【角度】【半径】设置圆形定位
效果图:
代码:

      app:layout_constraintCircle="@id/tv1"
      app:layout_constraintCircleAngle="30"
      app:layout_constraintCircleRadius="100dp"

特殊属性

约束限制

限制控件大小不会超过约束范围
效果图:
不约束的效果:
宽度超过了约束范围
约束后效果:
宽度在约束范围内
代码:

      app:layout_constrainedWidth="true"
      app:layout_constrainedHeight="true"

偏向

控制控件在水平方向20%的位置
效果图:
代码:

      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0.2"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"

还可以用于配合【约束限制】下,不需要居中的情况使用
效果图:


代码:

      app:layout_constrainedWidth="true"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0"
      app:layout_constraintStart_toEndOf="@id/tv1"
      app:layout_constraintTop_toTopOf="@id/tv1" 

goneMargin

当约束的view不显示时(gone),设置为这个margin
效果图:
显示时:
不显示时:
代码:

      android:layout_marginStart="10dp"
      app:layout_goneMarginLeft="30dp"

      app:layout_constraintStart_toEndOf="@id/tv1"

约束链

在约束链的第一个控件加上chainStyle,可以改变一组控件的布局方式:
spread(扩散,默认值)、packed(打包)、spread_inside(内部扩散)
效果图:
扩散:
打包:
内部扩散:
代码:

  <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:layout_centerInParent="true"
    android:background="#ffffff">


    <TextView
      android:id="@+id/tv1"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="@color/teal_200"
      app:layout_constraintHorizontal_chainStyle="packed"
      app:layout_constraintEnd_toStartOf="@id/tv2"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/tv2"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="#e33"
      app:layout_constraintEnd_toStartOf="@id/tv3"
      app:layout_constraintStart_toEndOf="@id/tv1"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/tv3"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="@color/teal_200"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toEndOf="@id/tv2"
      app:layout_constraintTop_toTopOf="parent" />


  </androidx.constraintlayout.widget.ConstraintLayout>

宽高比

至少需要一个方向的值为0dp
使用app:layout_constraintDimensionRatio="1:2"设置宽比高为1:2

百分比布局

需要对应方向的值为0dp
百分比是parent的百分比,而不是约束区域的百分比
使用app:layout_constraintWidth_percent="0.8"

辅助控件

GuideLine 参考线

添加一条虚拟的参考线,别的控件可以以这条线作为约束
效果图:
代码:

  <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:layout_centerInParent="true"
    android:background="#ffffff">

    <androidx.constraintlayout.widget.Guideline
      android:id="@+id/guideline1"
      app:layout_constraintGuide_percent="0.5"
      android:orientation="vertical"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"/>

    <TextView
      android:layout_marginTop="50dp"
      android:id="@+id/tv1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:background="@color/teal_200"
      android:text="账号名称:"
      android:textSize="20sp"
      app:layout_constraintEnd_toStartOf="@id/guideline1"
      app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatEditText
      android:hint="输入账号"
      app:layout_constraintTop_toTopOf="@id/tv1"
      app:layout_constraintBottom_toBottomOf="@id/tv1"
      app:layout_constraintStart_toEndOf="@id/guideline1"
      android:id="@+id/et1"
      android:textSize="15sp"
      android:layout_width="150dp"
      android:layout_height="wrap_content"/>

    <TextView
      android:id="@+id/tv2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:background="@color/teal_200"
      android:textSize="20sp"
      android:text="密码:"
      app:layout_constraintEnd_toEndOf="@id/guideline1"
      app:layout_constraintTop_toBottomOf="@id/tv1"/>

    <androidx.appcompat.widget.AppCompatEditText
      android:hint="输入密码"
      app:layout_constraintTop_toTopOf="@id/tv2"
      app:layout_constraintBottom_toBottomOf="@id/tv2"
      app:layout_constraintStart_toEndOf="@id/guideline1"
      android:id="@+id/et2"
      android:textSize="15sp"
      android:layout_width="150dp"
      android:layout_height="wrap_content"/>
    
  </androidx.constraintlayout.widget.ConstraintLayout>

Group 组

通过constraint_referenced_ids使用引用的方式来避免布局嵌套,可以统一控制一组控件的隐藏和显示(只能设置可见度,不能设置点击事件)
代码:

    <androidx.constraintlayout.widget.Group
      android:id="@+id/group"
      android:layout_width="wrap_content"
      app:constraint_referenced_ids="tv1,tv2,et1,et2"
      android:layout_height="wrap_content"/>
      
      findViewById<View>(R.id.group).visibility = View.GONE

Layer 层布局

和Group类似,为一组控件统一设置旋转、缩放、位移
todo

Barrier 屏障

设置一组控件某个方向的屏障
效果图:
上图中,左边两个标题一长一短,且谁长谁短不定,右边的文字需要按照标题做约束,这时就需要给两个标题添加end方向的屏障,然后右边文字按照屏障做约束
代码:

  <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:layout_centerInParent="true"
    android:background="#ffffff">

    <TextView
      android:background="@color/teal_200"
      android:id="@+id/tv1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="长一点的标题"
      android:textSize="15sp"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:background="@color/teal_200"
      android:id="@+id/tv2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="短标题"
      android:textSize="15sp"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@id/tv1" />

    <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="tv1,tv2" />

    <TextView
      android:background="#e33"
      android:id="@+id/tv3"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="10dp"
      android:layout_marginEnd="30dp"
      android:text="很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的文字测试"
      android:textSize="10sp"
      app:layout_constrainedWidth="true"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0"
      app:layout_constraintStart_toEndOf="@id/barrier"
      app:layout_constraintTop_toTopOf="parent" />

  </androidx.constraintlayout.widget.ConstraintLayout>

Placeholder 占位符

Placeholder的作用就是占位,它可以在布局中占好位置,然后通过setContentId()来设置内容,将某个控件移动到此占位符中。
可以配合TransitionManager.beginDelayedTransition(constraint_root)来实现过渡动画
效果图:
代码:

  <androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/constraint_root"
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:layout_centerInParent="true"
    android:background="#ffffff">

    <androidx.constraintlayout.widget.Placeholder
      android:id="@+id/placeholder"
      android:layout_width="50dp"
      android:layout_height="50dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/tv1"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="@color/teal_200"
      android:text="1"
      android:textAlignment="center"
      android:textSize="30sp"
      app:layout_constraintEnd_toStartOf="@id/tv2"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/tv2"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="#e33"
      android:text="2"
      android:textAlignment="center"
      android:textSize="30sp"
      app:layout_constraintEnd_toStartOf="@id/tv3"
      app:layout_constraintStart_toEndOf="@id/tv1"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/tv3"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="#3e3"
      android:text="3"
      android:textAlignment="center"
      android:textSize="30sp"
      app:layout_constraintEnd_toStartOf="@id/tv4"
      app:layout_constraintStart_toEndOf="@id/tv2"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/tv4"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="#33e"
      android:text="4"
      android:textAlignment="center"
      android:textSize="30sp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toEndOf="@id/tv3"
      app:layout_constraintTop_toTopOf="parent" />

  </androidx.constraintlayout.widget.ConstraintLayout>


        findViewById<View>(R.id.tv1).setOnClickListener {
            TransitionManager.beginDelayedTransition(constraint_root)
            placeholder.setContentId(R.id.tv1)
        }
        

Flow 流式虚拟布局

Flow的constraint_referenced_ids关联的控件是没有设置约束的
通过流式的方式对关联的控件进行布局,有3种flow_wrapMode(排列方式)可选:none(默认值)、chain、aligned。下面分别介绍每种排列方式:
none:所有引用的view形成一条链,水平居中,超出屏幕两侧的view不可见,效果如下:

chain:所有引用的view形成一条链,超出部分自动换行,同行的view会平分宽度,效果如下:

aligned:所有引用的view形成一条链,但view会在同行同列,效果如下:

代码:

  <androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/constraint_root"
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:layout_centerInParent="true"
    android:background="#ffffff">

    <!--flow test code  start-->
    <TextView
      android:id="@+id/tv1"
      android:layout_width="140dp"
      android:layout_height="50dp"
      android:background="@color/teal_200"
      android:text="1"
      android:textAlignment="center"
      android:textSize="30sp" />

    <TextView
      android:id="@+id/tv2"
      android:layout_width="140dp"
      android:layout_height="50dp"
      android:background="#e33"
      android:text="2"
      android:textAlignment="center"
      android:textSize="30sp" />

    <TextView
      android:id="@+id/tv3"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="#3e3"
      android:text="3"
      android:textAlignment="center"
      android:textSize="30sp" />

    <TextView
      android:id="@+id/tv4"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:background="#33e"
      android:text="4"
      android:textAlignment="center"
      android:textSize="30sp" />

    <TextView
      android:id="@+id/tv5"
      android:layout_width="40dp"
      android:layout_height="50dp"
      android:background="#33e"
      android:text="5"
      android:textAlignment="center"
      android:textSize="30sp" />


    <androidx.constraintlayout.helper.widget.Flow
      app:flow_horizontalStyle="packed"
      android:id="@+id/flow"
      app:flow_wrapMode="chain"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      app:constraint_referenced_ids="tv1,tv2,tv3,tv4,tv5"
      app:flow_horizontalGap="2dp"
      app:layout_constraintTop_toTopOf="parent"/>
    <!--flow test code  start-->
    
  </androidx.constraintlayout.widget.ConstraintLayout>

ConstraintSet

在传统布局中,如果想改变某个控件的位置,需要获取LayoutParams,然后修改属性值,但是在约束布局中,要改变控件的约束条件,需要使用ConstraintSet
使用步骤:

  1. 创建ConstraintSet
  2. 需要复制一份父布局的约束(也可以从已有的xml文件中复制)
  3. 设置组件之间的约束(或者移除之前的约束)
  4. 设置过渡动画
  5. apply更新约束条件
    代码:
        val constraint_root = findViewById<ConstraintLayout>(R.id.constraint_root)
        val v1 = findViewById<View>(R.id.v1)
        v1.setOnClickListener {
            val constraintSet = ConstraintSet().apply {
                isForceId = false//防止布局中有无id控件时报错,需要设置isForceId=false
                //clone(this@ConstraintActivity,R.layout.activity_constraint2)  //从xml中复制约束条件
                clone(constraint_root)
                clear(R.id.v1, ConstraintSet.START)//移除约束
                clear(R.id.v1, ConstraintSet.TOP)
                connect(//重新建立约束
                    R.id.v1,
                    ConstraintSet.BOTTOM,
                    ConstraintSet.PARENT_ID,
                    ConstraintSet.BOTTOM
                )
                connect(
                    R.id.v1,
                    ConstraintSet.END,
                    ConstraintSet.PARENT_ID,
                    ConstraintSet.END
                )
            }
            TransitionManager.beginDelayedTransition(constraint_root)//过渡动画
            constraintSet.applyTo(constraint_root)//约束条件更新
        }

ConstraintHelper

以上的Group、Barrier、Layer、Flow都是继承自ConstraintHelper,ConstraintHelper 继承自 View,因此它可以在 xml 中使用和预览。但它通常没有自己的 UI 样式,只是通过特定的逻辑,去控制其他相关 View 的约束。

它有一个constraint_referenced_ids属性,也就是我们在用Group的时候指定相关 View id 的属性。此类主要作用就是将 xml 中赋值的 id 字符串(如"v1, v2, v3")解析成一组真正的 int 类型的 View id(定义在 R.java 文件中),然后通过getReferencedIds()方法提供给子类使用。