安卓开发中各种布局方式总结

通用布局属性

属性名称功能
android:id设置布局的标识
android:layout_width设置布局的宽度
android:layout_height设置布局的高度
android:layout_margin设置当前布局与屏幕边界或与周围控件的距离
android:padding设置当前布局与该布局中控件的距离
android:background设置布局的背景
android:visibilityvisible:显示;invisible:不显示但是占用空间;gone:不显示也不占用空间;
android:enabled是否启用该控件
android: focusable是否可以获取焦点

其中 padding 和 margin 可以分别控制单独上下左右,比如 paddingTop, paddingLeft, marginRight 等。

LinearLayout(线性布局):

  • 用途:
    LinearLayout是最简单的布局,它按照水平或垂直方向排列子视图。有点类似 css 中的 flex 布局。可以 LinearLayout 中嵌套 LinearLayout/RelativeLayout 等布局使用。

  • 属性:

    属性名称功能
    android:orientation设置布局内控件的排列顺序(horizontal 或 vertical)
    android:weightSum定义了weight 总和的最大值(单独设置没效果)。
    可以不设置,如果不设置,会根据子组件所设置的 layout_weight 计算出 weightSum 的值。
    android:layout_weight在布局内设置子控件权重,属性值可直接写int,控件的大小剩余空间将按比例来分配。
    horizontal 配合 android:layout_width=“xxxdp” 使用
    vertical 配合 android:layout_height=“xxxdp” 使用。
    android:gravity控制该组件所包含的子元素的对齐方式,可以多个组合,如(left
    写在父组件里,控制子组件。
    android:layout_gravity控制子组件在父组件中的位置。
    写在子组件上,控制自己在父组件中的位置。会覆盖父组件的 android:gravity 设置

剩余空间,他指的是在布局方向上,除去所有组件自身所占空间和 margin 所占空间之后,剩余的值。以水平方向布局为例:水平方向总宽度为 700dp,并且有组件A和B,组件A设置 layout_width=200dp;layout_margin=50dp; 组件B设置 layout_width=100dp;那么水平方向剩余空间为:700-50-200-50-100=300dp。该属性的主要作用可以用来做适配 。
这时,我们将剩余空间(300dp)分为3份(weightSum=3),组件A占2份(layout_weight=2),组件B占1份(layout_weight=1)。
此时,我们不难发现,该水平方向空间占比依次为:marginLeft:50dp ---- 组件A:400dp(200dp+200dp) ---- marginRight:50dp ---- 组件B:200dp(100dp+100dp)。

  • 样例:

    竖向分布的3个按钮按照 1:1:2 均分父视图高度。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <Button
        android:text="按钮1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        />
    <Button
        android:text="按钮2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
    />
    <Button
        android:text="按钮3"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        />
</LinearLayout>

给其中的按钮 1 添加点 margin 和 padding 效果图如下:

<Button
        android:text="按钮1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="200dp"
        android:paddingTop="50dp"
        android:layout_weight="1"
        />

这里可以看到 view 的 layout_width、layout_height、layout_left、layout_right、layout_start、layout_end、layout_top、layout_bottom 均包含 padding 但是不包含 layout_margin。
这个和 css 布局中的 IE 盒子模型(box-sizing: border-box)比较像。区别在于,Android中没有 border 这个东西,而且在 Android 中,margin 并不是控件的一部分。

Android 可以通过设置背景图元素达到设置边框的效果,下面两种设置方式都可以:

border_bg_01.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <!-- 边框颜色 -->
            <solid android:color="#FF0000" />
        </shape>
    </item>

    <!--给View的上 左 下 右   设置2dp的边框 -->
    <item android:top="2dp" android:left="2dp"
        android:right="2dp" android:bottom="2dp">
        <shape>
            <!-- View填充颜色 -->
            <solid android:color="#FFFFFF" />
        </shape>
    </item>
</layer-list>

border_bg_02.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FFFFFF" />
    <stroke android:width="2dp" android:color="#f00"></stroke>
    <padding android:left="2dp" android:top="2dp" android:right="2dp" android:bottom="2dp" />
</shape>

修改上面的 LinearLayout 容器的 background 属性如下:

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="@drawable/border_bg_01"
        tools:context=".LayoutStudyActivity">

另一个展示 layout_gravity、layout_weight 的例子。

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:weightSum="3"
    android:gravity="bottom"
    tools:context=".LayoutStudyActivity">
    <View
        android:layout_width="100dp"
        android:layout_margin="50dp"
        android:layout_height="300dp"
        android:layout_gravity="center"
        android:layout_weight="2"
        android:background="@color/teal_700"
        />
    <View
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@color/sky"
        />
</LinearLayout>

RelativeLayout(相对布局)、PercentRelativeLayout(百分比相对布局):

  • 用途:
    RelativeLayout 允许子视图相对于父视图或其他子视图定位。通过指定视图之间的相对关系,可以实现灵活的布局效果。
    PercentRelativeLayout 允许子视图设置相对于父视图的百分比。

  • 属性:

属性名称功能
android:layout_centerInParent设置当前控件位于父布局的中央位置
android:layout_centerVertical设置当前控件位于父布局的垂直居中位置
android:layout_centerHorizontal设置当前控件位于父控件的水平居中位置
android:layout_alignParentTop设置当前控件是否与父控件顶端对齐
android:layout_alignParentBottom设置当前控件是否与父控件底端对齐
android:layout_alignParentLeft设置当前控件是否与父控件左对齐
android:layout_alignParentRight设置当前控件是否与父控件右对齐
android:layout_above设置当前控件位于某控件上方
android:layout_below设置当前控件位于某控件下方
android:layout_toLeftOf设置当前控件位于某控件左侧
android:layout_toRightOf设置当前控件位于某控件右侧
android:layout_alignTop设置当前控件是否与父控件上边界对齐
android:layout_alignBottom设置当前控件是否与父控件下边界对齐
android:layout_alignLeft设置当前控件是否与父控件左边界对齐
android:layout_alignRight设置当前控件是否与父控件右边界对齐

注意:
上述居中对齐于父视图相关(当前视图的 border-box 中不含 padding 的 content 区域与父视图中含 padding 的 border-box 盒子居中,非常特殊)。
上述与父视图对齐相关(当前视图的包含 margin 的 content-box 盒子与父视图的 border-box 里面除去 padding 的 content 区域对齐)。
上述与其它子视图的对齐相关(当前视图的包含 margin 的 content-box 盒子区域与其它视图的包含 margin 的 content-box 盒子区域对齐)。

PercentRelativeLayout 另外支持的属性:

属性名称功能
app:layout_widthPercent设置子视图宽度和父布局宽度的百分比(例如:50%)
app:layout_heightPercent设置子视图高度和父布局高度的百分比(例如:50%)
app:layout_marginPercent
app:layout_marginLeftPercent
app:layout_marginTopPercent
app:layout_marginRightPercent
app:layout_marginBottomPercent
app:layout_marginStartPercent
app:layout_marginEndPercent
app:layout_aspectRatio确定宽度或者高度的情况下,设置宽高比
  • 样例:

固定宽高的 6 个按钮分别放在父视图的左上、右上、中间、左下、右下角、中间上面的位置。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:layout_marginLeft="20dp"
    android:paddingLeft="50dp">
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="50dp"
        android:text="左上角"
        android:textSize="30dp" />
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentBottom="true"
        android:text="左下角"
        android:textSize="30dp" />
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentRight="true"
        android:text="右上角"
        android:textSize="30dp" />
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="右下角"
        android:textSize="30dp" />
    <Button
        android:id="@+id/center_button"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:layout_marginTop="20dp"
        android:text="中间"
        android:textSize="30dp" />
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_above="@id/center_button"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="20dp"
        android:text="中间上方"
        android:textSize="30dp" />
</RelativeLayout>

上述例子中的 layout_width/height、layout_margin相关 改成百分比如下:

<androidx.percentlayout.widget.PercentRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:layout_marginLeft="20dp"
    android:paddingLeft="50dp"
    android:background="@color/cyan">
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_alignParentLeft="true"
        app:layout_marginLeftPercent="10%"
        android:text="左上角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_alignParentBottom="true"
        android:text="左下角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_alignParentRight="true"
        android:text="右上角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="右下角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/center_button"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_centerInParent="true"
        app:layout_marginTopPercent="5%"
        android:text="中间"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_above="@id/center_button"
        android:layout_centerHorizontal="true"
        app:layout_marginBottomPercent="5%"
        android:text="中间上方"
        android:textSize="15dp" />
</androidx.percentlayout.widget.PercentRelativeLayout>

设置图片的宽高比为 16: 9

<androidx.percentlayout.widget.PercentRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/teal_700"
        android:layout_margin="10dp"
        app:layout_widthPercent="100%"
        app:layout_aspectRatio="178%"/>
</androidx.percentlayout.widget.PercentRelativeLayout>

FrameLayout(帧布局)、PercentFrameLayout(百分比帧布局):

  • 用途:
    FrameLayout 将子视图堆叠在一起,每个子视图位于最顶层的位置。常用于覆盖显示或切换视图。
    PercentFrameLayout 允许子视图设置相对于父视图的百分比。

  • 属性:

属性名称功能
android:foreground设置帧布局容器的前景图像
android:foregroundGravity设置前景图像显示的位置
android:layout_gravity用于子视图设置自己在父视图中的对齐方式

PercentFrameLayout 另外支持的属性

属性名称功能
app:layout_widthPercent设置当前控件宽度和父布局宽度的百分比(例如:50%)
app:layout_heightPercent设置当前控件高度和父布局高度的百分比(例如:50%)
app:layout_marginPercent
app:layout_marginLeftPercent
app:layout_marginTopPercent
app:layout_marginRightPercent
app:layout_marginBottomPercent
app:layout_marginStartPercent
app:layout_marginEndPercent
app:layout_aspectRatio确定宽度或者高度的情况下,设置宽高比
  • 样例:

图片放在几个重叠视图的最上方

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:foreground="@mipmap/ic_launcher"
    android:foregroundGravity="center">
    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/teal_200"
        tools:ignore="SpeakableTextPresentCheck" />
    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="100dp"
        android:background="@color/cyan"
        tools:ignore="SpeakableTextPresentCheck" />
    <TextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:layout_marginTop="100dp"
        android:background="@color/teal_700"/>
</FrameLayout>

PercentRelativeLayout 中的例子去掉中间上方的按钮样式如下:

<androidx.percentlayout.widget.PercentFrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:layout_marginLeft="20dp"
    android:paddingLeft="50dp">
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        app:layout_marginLeftPercent="10%"
        android:layout_gravity="left|top"
        android:text="左上角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_gravity="left|bottom"
        android:text="左下角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_gravity="right|top"
        android:text="右上角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_gravity="right|bottom"
        android:text="右下角"
        android:textSize="15dp" />
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="30%"
        app:layout_heightPercent="30%"
        android:layout_gravity="center"
        app:layout_marginTopPercent="5%"
        android:text="中间"
        android:textSize="15dp" />
</androidx.percentlayout.widget.PercentFrameLayout>

仔细对比会看到中间的按钮位置往右往下移动了。
上述各种 layout_gravity 控制的与父视图对齐(当前视图的包含 margin 的 content-box 盒子与父视图的 border-box 里面除去 padding 的 content 区域对齐)。注意居中与上面的 RelativeLayout 的不同。

ConstraintLayout(约束布局):

  • 用途:
    ConstraintLayout 是一个灵活强大的布局,可以实现复杂的界面布局。它使用约束将子视图相对于父视图或其他子视图进行定位。
    功能强大,ConstraintLayout 几乎能实现其他布局所有的功能。
    能减少布局层次的嵌套,有性能的优势。
    可视化操作的增强,大部分界面用 ConstraintLayout 都能通过可视化编辑区域完成。
    它的灵活性和性能使得它成为 Android 开发中常用的布局方式之一。
  1. 相对定位:可以通过约束条件将视图相对于父容器或其他视图进行定位,而不需要使用嵌套布局。
  2. 完全由约束决定宽高:android:layout_width=“0dp” 或者 android:layout_height=“0dp”:代表完全由约束决定宽高,
    另外配合 app:layout_constraintWidth_percent=“0.3” 和 app:layout_constraintHeight_percent=“0.5” 属性可以实现百分比布局。
  3. 按照比例分配:子视图按比例分配父视图的宽或高的剩余空间。
  4. 上下左右空余按比例设置间距(bias):默认值为 0.5,表示两边等分剩余空间。可用于设置子视图相对于父视图的上下左右居中等对齐。
  5. 百分比布局:控件的尺寸按照父布局尺寸的百分比来设置。(只有上面的宽度和高度尺寸为0dp百分比属性才会生效,见上一条描述)
  6. 宽高比:如果宽高都是 0dp 也可以使用宽高比,这种情况系统会使用满足所有约束条件和宽高比率的最大尺寸。
    如果要根据其中一个尺寸来约束另外一个尺寸,可以在比率数值前添加 “W/H” 来分别约束宽度或者高度。
    例如:给图片宽度设置match_parent,高度设置成约束尺寸0dp,然后再添加一个layout_constraintDimensionRatio 属性,属性的值可以设置h,16:9 其中h表示宽高比的比值,也可以设置成将值设置w,9:16,w表示高宽比的比值。
  7. goneMargin:用来控制当约束目标可见性为 gone 的时候,设置不同的间距值。
    A 不可见的时候,需要保证 B 的布局位置不变,这个时候设置 B
    的 goneMarginStart 的值为 A 的宽度加上 A 的 marginStart 加上 B 的 marginStart ,就可以满足该需求。
  8. wrap_content 下的强制约束:可用于在使用 wrap_content 但仍然需要强制约束条件来限制它的宽高(不让其过宽或过高)。
  9. 链式布局(chain):chain 为同一个方向(水平或者垂直)上的多个控件提供一个类似群组的概念。其他的方向则可以单独控制。
    多个控件相互在同一个方向上双向引用就可以创建一个 chain。
    chain 链也能够支持按照 weight 等分剩余空间。
    chain 链对父控件的剩余空间有三种分配方式,即 spread、spread inside、packed,默认值是 spread 即将控件包括第一个控件和最后一个两边均匀分配。其它两种作用参照下图。

  1. 圆形定位:将一个控件的中心以一定的角度和距离约束到另一个控件的中心上,相当于在一个圆上放置一个控件。

  1. 辅助线:Guideline 是 ConstraintLayout 布局里面的一个工具类,其作用相当于一条辅助线,默认不可见,可以用于辅助布局。
  2. 屏障:Barrier 和 Guideline 一样,本身不可见,用户辅助布局,可以选择关联的视图某个方向上的最大值为其位置,方便其它视图和该 Barrier 设置约束关系。
  3. 可视化分组:Group 可以对一组的控件同时设置其可见性的值Visible、Invisible或者Gone,以及同时设定深度(elevation)属性。
    被 Group 引用的控件会导致它们的自身的可见性和深度失效。
  4. ConstraintSet 可用于在 ConstraintLayout 中动态修改约束的类。可以通过 ConstraintSet 在运行时改变界面布局。 还可以使用 ConstraintSet 实现关键帧动画。
  • 属性:
  1. 给父布局添加约束
属性名称功能
app:layout_constraintBottom_toBottomOf=“parent”底部约束
app:layout_constraintEnd_toEndOf=“parent”右边约束
app:layout_constraintStart_toStartOf=“parent”左边约束
app:layout_constraintTop_toTopOf=“parent”顶部约束
  1. 将约束添加到其他控件的属性
属性名称功能
app:layout_constraintBottom_toTopOf=“@+id/button3”底部添加约束到其他控件顶部
app:layout_constraintEnd_toEndOf=“@+id/button3”右边添加约束到其他控件右边
app:layout_constraintStart_toStartOf=“@+id/button3”左边添加约束到其他控件左边
  1. 完全由约束决定宽高
属性名称功能
android:layout_width=“0dp”完全由约束决定宽度
android:layout_height=“0dp”完全由约束决定高度

比如很多app的首页都是由底部 banner 和顶部 fragment 组成,而 fragment 的高度通常不是固定的,如果用其他的布局 fragment 的高度就很难调节,
这时候可以用 0dp(代表由约束决定)动态调节 fragment 的尺寸高度,给顶部的 fragment 设置其高度为 0dp,给其上下各添加一个约束,这时候其高度为父布局的顶部到底部控件的顶部。

<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"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textView5"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="@color/teal_700"
        android:gravity="center"
        android:text="顶部fragment"
        app:layout_constraintBottom_toTopOf="@+id/textView4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/textView4"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/purple_200"
        android:text="底部banner"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

给上面的一些视图添加 padding 和 margin 看一下对布局的计算影响,修改如下:

<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:layout_marginBottom="20dp"
    android:layout_marginStart="20dp"
    android:paddingStart="20dp"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textView5"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="20dp"
        android:background="@color/teal_700"
        android:gravity="center"
        android:text="顶部fragment"
        app:layout_constraintBottom_toTopOf="@+id/textView4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/textView4"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginStart="20dp"
        android:background="@color/purple_200"
        android:text="底部banner"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

可以发现:
上述与父视图对齐相关(当前视图的包含 margin 的 content-box 盒子与父视图的 border-box 里面除去 padding 的 content 区域对齐)。
上述与其它子视图的对齐相关(当前视图的包含 margin 的 content-box 盒子区域与其它视图的不包含 margin 的 border-box 盒子区域对齐),这点非常特殊。

  1. 按比例分配
属性名称功能
app:layout_constraintHorizontal_weight配合 layout_width=“0dp” 使用,与 LinearLayout 中作用一样
app:layout_constraintVertical_weight配合 layout_height=“0dp” 使用,与 LinearLayout 中作用一样

放置 4 个按钮分别在左上、左下、右上、右下位置,等分宽高。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:layout_marginLeft="20dp"
    android:paddingLeft="50dp">
    <Button
        android:id="@+id/button_top_left"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="左上角"
        android:textSize="30dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="@id/button_top_right"
        app:layout_constraintBottom_toTopOf="@id/button_bottom_left"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintVertical_weight="1"
        android:layout_marginLeft="50dp"/>
    <Button
        android:id="@+id/button_bottom_left"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="左下角"
        android:textSize="30dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/button_top_left"
        app:layout_constraintEnd_toStartOf="@id/button_bottom_right"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintVertical_weight="1"/>
    <Button
        android:id="@+id/button_top_right"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="右上角"
        android:textSize="30dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/button_top_left"
        app:layout_constraintBottom_toTopOf="@id/button_bottom_right"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintVertical_weight="1"
        android:layout_marginLeft="50dp"/>
    <Button
        android:id="@+id/button_bottom_right"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="右下角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/button_bottom_left"
        app:layout_constraintTop_toBottomOf="@id/button_top_right"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintVertical_weight="1"/>
</androidx.constraintlayout.widget.ConstraintLayout>

  1. bias
属性名称功能
app:layout_constraintHorizontal_bias左边占总横向剩余空间的比例(默认为0.5)
app:layout_constraintVertical_bias上边占总竖向剩余空间的比例(默认为0.5)

下面这个例子实现想实现和上面 RelativeLayout 中样例一样的效果。
固定宽高的 6 个按钮分别放在父视图的左上、右上、中间、左下、右下角、中间上面的位置。

<?xml version="1.0" encoding="utf-8"?>
<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/teal_200"
    android:layout_marginLeft="20dp"
    android:paddingLeft="50dp">
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:text="左上角"
        android:textSize="30dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="50dp"/>
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:text="左下角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1"/>
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:text="右上角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0"/>
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:text="右下角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1"/>
    <Button
        android:id="@+id/center_button"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:text="中间"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="20dp"/>
    <Button
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:text="中间上方"
        android:textSize="30dp"
        app:layout_constraintBottom_toTopOf="@id/center_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginBottom="20dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

上述与父视图对齐相关(当前视图的包含 margin 的 content-box 盒子与父视图的 border-box 里面除去 padding 的 content 区域对齐)。
上述与其它子视图的对齐相关(当前视图的包含 margin 的 content-box 盒子区域与其它视图的不包含 margin 的 border-box 盒子区域对齐),这点非常特殊。
(中间的那个按钮的 marginTop 设置任意值都不会影响到中间上方的按钮位置)。

  1. 百分比布局
属性名称功能
app:layout_constraintWidth_percent宽度占父布局百分比比例(例如:0.3,代表占30%父视图宽度)
app:layout_constraintHeight_percent高度占父布局百分比比例(例如:0.3,代表占30%父视图宽度)

修改上面的 bias 中的样例,如下:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:layout_marginLeft="20dp"
    android:paddingLeft="50dp">
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintHeight_percent="0.3"
        android:text="左上角"
        android:textSize="30dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="50dp"/>
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintHeight_percent="0.3"
        android:text="左下角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1"/>
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintHeight_percent="0.3"
        android:text="右上角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0"/>
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintHeight_percent="0.3"
        android:text="右下角"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1"/>
    <Button
        android:id="@+id/center_button"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintHeight_percent="0.3"
        android:text="中间"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="20dp"/>
    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintHeight_percent="0.3"
        android:text="中间上方"
        android:textSize="30dp"
        app:layout_constraintBottom_toTopOf="@id/center_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginBottom="20dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

  1. 宽高比
属性名称功能
app:layout_constraintDimensionRatio宽高都不确定的情况下,可以设置一个float值。
其中宽或高一个确定情况下,另一个为 0dp,
属性的值可以设置h,16:9 其中h表示宽高比的比值,也可以设置成将值设置w,9:16,w表示高宽比的比值
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:text="宽高比为16:9"
        android:textSize="20dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:background="@color/teal_700"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintDimensionRatio="h,16:9"/>
</androidx.constraintlayout.widget.ConstraintLayout>

  1. goneMargin
属性名称功能
app:goneMarginStart用来控制当约束目标可见性为 gone 的时候,设置不同的间距值。
app:goneMarginEnd同app:goneMarginStart。
app:goneMarginTop同app:goneMarginStart。
app:goneMarginBottom同app:goneMarginStart。

A 不可见的时候,需要保证 B 的布局位置不变,这个时候设置 B
的 goneMarginStart 的值为 A 的宽度加上 A 的 marginStart 加上 B 的 marginStart ,就可以满足该需求。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:id="@+id/left_view"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="20dp"
        android:background="@color/teal_200"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="visible"/>
    <View
        android:id="@+id/right_view"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:background="@color/teal_700"
        android:layout_marginStart="20dp"
        app:layout_goneMarginStart="90dp"
        app:layout_constraintStart_toEndOf="@id/left_view"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

  1. wrap_content 下的 layout_constrainedWidth/Height 强制约束
属性名称功能
app:layout_constrainedWidthtrue/false,width 为 wrap_content 时仍需满足约束
app:layout_constrainedHeighttrue/false,height 为 wrap_content 时仍需满足约束

某个元素使用 app:layout_constraintHorizontal_chainStyle=“packed”,将会使相互水平方向上有相互对称依赖约束的子视图表现的像一个整体,同时其内部是一个单行 chain,超长也不会换行,支持 packed、spread、spread_inside 三种模式。

头像位置固定,中间文字长度可变,最右侧按钮跟在文字右侧,但不能超出屏幕(packed)。
头像位置固定,中间文字长度可变,文字过长会显示…,最右侧按钮始终在最右侧(spread_inside)。
上述两种情况去掉 layout_constrainedWidth 属性设置,则过长情况下都会超出显示,不满足约束条件。

<?xml version="1.0" encoding="utf-8"?>
<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="60dp"
    android:background="@color/teal_700">
    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginStart="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:src="@mipmap/luffy"/>
    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="15dp"
        android:singleLine="true"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="@+id/iv_avatar"
        app:layout_constraintEnd_toStartOf="@id/tv_action"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toEndOf="@id/iv_avatar"
        app:layout_constraintTop_toTopOf="@+id/iv_avatar"
        android:text="ConstraintLayout is available as a support library"/>
    <TextView
        android:id="@+id/tv_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="15dp"
        app:layout_constraintBottom_toBottomOf="@+id/iv_avatar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/tv_text"
        app:layout_constraintTop_toTopOf="@+id/iv_avatar"
        android:text="查看"/>
</androidx.constraintlayout.widget.ConstraintLayout>

  1. 链式布局(chain)
属性名称功能
app:layout_constraintHorizontal_chainStylepacked、spread、spread_inside
app:layout_constraintHorizontal_weight需要搭配子视图的 0dp
app:layout_constraintVertical_chainStyle同上
app:layout_constraintVertical_weight同上
app:layout_constraintHorizontal_bias需要 style 为 packed 时使用
app:layout_constraintVertical_bias需要 为 packed 时使用

使 chain 中子视图按照 layout_constraintHorizontal_weight 比例均分。

<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="60dp"
    android:background="@color/teal_700">
    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginStart="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:src="@mipmap/luffy"/>
    <TextView
        android:id="@+id/tv_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="15dp"
        android:background="@color/gray"
        android:textColor="@color/white"
        android:singleLine="true"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintBottom_toBottomOf="@+id/iv_avatar"
        app:layout_constraintEnd_toStartOf="@id/tv_action"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toEndOf="@id/iv_avatar"
        app:layout_constraintTop_toTopOf="@+id/iv_avatar"
        android:text="ConstraintLayout is available as a support library"/>
    <TextView
        android:id="@+id/tv_action"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="15dp"
        android:background="@color/teal_200"
        android:textColor="@color/white"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintBottom_toBottomOf="@+id/iv_avatar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/tv_text"
        app:layout_constraintTop_toTopOf="@+id/iv_avatar"
        android:text="查看"/>
</androidx.constraintlayout.widget.ConstraintLayout>

右侧图片和文字,需要整体跟左边头像居中。
某个元素使用 app:layout_constraintHorizontal_chainStyle=“packed”,将会使可以让某一个方向上相互对称依赖约束的子视图表现的像一个整体,
同时其内部是一个单行 chain,超长也不会换行,支持 packed、spread、spread_inside 三种模式。

<androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="300dp"
      app:layout_constraintTop_toTopOf="parent"
      android:background="@color/teal_700">

      <ImageView
          android:id="@+id/iv_avatar"
          android:layout_width="150dp"
          android:layout_height="150dp"
          android:layout_marginStart="15dp"
          app:layout_constraintBottom_toBottomOf="parent"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintHorizontal_bias="0"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintVertical_bias="0.3"
          android:src="@mipmap/luffy" />

      <ImageView
          android:id="@+id/iv_pic"
          android:layout_width="30dp"
          android:layout_height="30dp"
          android:layout_marginStart="50dp"
          android:scaleType="centerCrop"
          app:layout_constraintBottom_toTopOf="@+id/tv_content"
          app:layout_constraintStart_toEndOf="@id/iv_avatar"
          app:layout_constraintTop_toTopOf="@id/iv_avatar"
          app:layout_constraintVertical_chainStyle="packed"
          android:src="@mipmap/luffy2" />

      <TextView
          android:id="@+id/tv_content"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:layout_marginTop="5dp"
          android:textColor="@color/white"
          app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toStartOf="@id/iv_pic"
          app:layout_constraintTop_toBottomOf="@+id/iv_pic"
          android:text="chain 可以让某一个方向上相互对称依赖约束的子视图表现的像一个整体。 " />
</androidx.constraintlayout.widget.ConstraintLayout>

  1. 圆形定位
属性名称功能
app:layout_constraintCircle引用另一个控件的 id。
app:layout_constraintCircleRadius到另一个控件中心的距离。
app:layout_constraintCircleAngle控件的角度(顺时针,0 - 360 度)。
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:id="@+id/view_one"
        android:background="@color/teal_700"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />
    <View
        android:id="@+id/view_two"
        android:background="@color/teal_200"
        android:layout_width="20dp"
        android:layout_height="20dp"
        app:layout_constraintCircle="@id/view_one"
        app:layout_constraintCircleRadius="100dp"
        app:layout_constraintCircleAngle="60" />
</androidx.constraintlayout.widget.ConstraintLayout>

  1. 辅助线(Guideline)
属性名称功能
android:orientationvertical/horizontal
app:layout_constraintGuide_begin指定距离左/上边开始的固定位置
app:layout_constraintGuide_end指定距离右/下边开始的固定位置
app:layout_constraintGuide_percent指定位于布局中所在的百分比
<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">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.3" />

    <View
        android:id="@+id/view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/cyan"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/teal_700"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>

  1. 屏障(Barrier)
属性名称功能
app:barrierDirection选择关联的视图中哪个方向的最大值
app:contraint_referenced_ids取那些视图的 direction 最大值为自己的位置
<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">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:text="short text info"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:text="long text info to show"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="right"
        app:constraint_referenced_ids="textView,textView2" />

    <View
        android:id="@+id/view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/teal_700"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/barrier2"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

  1. 可视化分组(Group)
属性名称功能
android:visibility可见性
android:elevation深度
app:constraint_referenced_ids选入分组的视图 id 列表
<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">

    <View
        android:id="@+id/view1"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/cyan"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/view2"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/teal_700"
        app:layout_constraintBottom_toTopOf="@+id/view3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/view1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_weight="1" />

    <View
        android:id="@+id/view3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/teal_200"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/view2"
        app:layout_constraintTop_toBottomOf="@+id/view2"
        app:layout_constraintVertical_weight="1" />

    <androidx.constraintlayout.widget.Group
        android:id="@+id/group"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="visible"
        app:constraint_referenced_ids="view2,view3" />
</androidx.constraintlayout.widget.ConstraintLayout>

FlowLayout(流式布局):

  • 用途:
    根据 ViewGroup 的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行(也可以是按垂直方向流式布局)。和 css 中的 float 布局非常相似。

    注意:Flow 管理的 view 自身可以不设置 layout_constraintStart_toEndOf 等位置。自身需要 width=“0dp”。关联的视图 app:layout_constraintWidth_default=“wrap”,如果是 TextView 最好设置 android:singleLine=“true”。

  • 属性:

属性名称功能
android:orientationhorizontal、vertical,水平方向的流式,还是垂直方向。
app:flow_wrapModenone(默认值):不换行
chain:换行
aligned:换行并且上下对齐
app:flow_horizontalGap横向行间距
app:flow_verticalGap纵向行间距
app:flow_verticalAligntop、bottom、center、baseline,在 android:orientation 为 horizontal 时有效
app:flow_horizontalAlignstart、end、center,在 android:orientation 为 vertical 时有效
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_horizontalBiaswrapMode 为 chian 时并且 style 为 packed 时有效
app:flow_verticalBiaswrapMode 为 chian 时并且 style 为 packed 时有效
app:flow_firstHorizontalBiaswrapMode 为 chian 时并且 style 为 packed 时有效
app:flow_firstVerticalBiaswrapMode 为 chian 时并且 style 为 packed 时有效
app:flow_lastHorizontalBiaswrapMode 为 chian 时并且 style 为 packed 时有效
app:flow_lastVerticalBiaswrapMode 为 chian 时并且 style 为 packed 时有效
app:flow_maxElementsWrap配置每行(horizontal)或者每列(vertical)最多的视图数量

flow_wrap 为 none 一行放不下也不换行。

flow_wrap 为 chain 一行放不下则换行,最后一行放不下,默认 style 为 spread。

flow_wrap 为 aligned 则所有列按照最大的宽度决定,并且对齐。

可以使用下面的样式测试各种属性效果:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:flow_horizontalGap="10dp"
        app:flow_verticalGap="10dp"
        app:flow_wrapMode="chain"
        app:flow_horizontalStyle="packed"
        app:flow_verticalAlign="top"
        app:flow_horizontalBias="0"
        app:flow_maxElementsWrap="5"
        app:constraint_referenced_ids="image1,image2,image3,image4,image5,image6,image7,image8,image9" />

    <ImageView
        android:id="@+id/image1"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image2"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image3"
        android:layout_width="130dp"
        android:layout_height="130dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image4"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image5"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image6"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image7"
        android:layout_width="130dp"
        android:layout_height="130dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image8"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@mipmap/ic_launcher" />

    <ImageView
        android:id="@+id/image9"
        android:layout_width="110dp"
        android:layout_height="110dp"
        android:src="@mipmap/ic_launcher" />

</androidx.constraintlayout.widget.ConstraintLayout>

下面的样例:
第一行显示一个从左到右拉通的文本,右边设置间距避免和右边的按钮重叠,过长则尾部显示…
第二行设置 0.3 水平辅助线,左边为需要换行文本,中间为 chain,右边一个固定按钮。

<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="wrap_content">

    <View
        android:id="@+id/top_margin"
        android:layout_width="match_parent"
        android:layout_height="8dp"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/teal_700"
        android:paddingHorizontal="12dp"
        android:paddingVertical="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/top_margin">

        <TextView
            android:id="@+id/tv_device_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginRight="70dp"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="@color/white"
            android:textSize="15sp"
            android:textStyle="bold"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:text="这是一段很长很长很长很长很长很长很长很长的文字" />

        <TextView
            android:id="@+id/tv_org_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:textColor="@color/black"
            android:textSize="14sp"
            app:layout_constraintEnd_toStartOf="@id/guideline"
            app:layout_constraintHorizontal_bias="0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_device_name"
            app:layout_constraintWidth_default="wrap"
            android:text="这也是一段很长很长很长很长很长很长很长很长的文字" />

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


        <androidx.constraintlayout.helper.widget.Flow
            android:id="@+id/flow"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dp"
            android:layout_marginEnd="5dp"
            app:constraint_referenced_ids="tv_type,tv_online_state,tv_switch_state"
            app:flow_firstHorizontalBias="0"
            app:flow_firstHorizontalStyle="packed"
            app:flow_horizontalBias="0"
            app:flow_horizontalGap="10dp"
            app:flow_lastHorizontalBias="0"
            app:flow_lastHorizontalStyle="packed"
            app:flow_verticalGap="8dp"
            app:flow_wrapMode="chain"
            app:layout_constraintEnd_toStartOf="@id/ibt_go"
            app:layout_constraintStart_toEndOf="@id/guideline"
            app:layout_constraintTop_toTopOf="@id/tv_org_name" />

        <TextView
            android:id="@+id/tv_type"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/blue"
            android:paddingHorizontal="8.5dp"
            android:paddingVertical="3dp"
            android:singleLine="true"
            android:textColor="@color/black"
            android:textSize="12sp"
            app:layout_constraintWidth_default="wrap"
            android:text="标签1的文案" />

        <TextView
            android:id="@+id/tv_online_state"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/yellow"
            android:paddingHorizontal="8.5dp"
            android:paddingVertical="3dp"
            android:singleLine="true"
            android:textColor="@color/black"
            android:textSize="12sp"
            app:layout_constraintWidth_default="wrap"
            android:text="标签2" />

        <TextView
            android:id="@+id/tv_switch_state"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/sky"
            android:paddingHorizontal="8.5dp"
            android:paddingVertical="3dp"
            android:singleLine="true"
            android:textColor="@color/black"
            android:textSize="12sp"
            app:layout_constraintWidth_default="wrap"
            android:text="标签3的文案也很长怎么办" />

        <androidx.appcompat.widget.AppCompatImageButton
            android:id="@+id/ibt_go"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            android:src="@mipmap/icon_arrow_2"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

AbsoluteLayout(绝对布局):

注:已被弃用,且不建议使用。

它以绝对坐标的方式精确定位视图,即可以通过指定相对于父容器左上角的精确坐标来确定视图的位置。

在 AbsoluteLayout 中,每个子视图的位置和大小都是通过设置其 android:layout_x 和 android:layout_y 属性来确定的。

使用 AbsoluteLayout 的优点是可以精确地控制视图的位置和布局,适用于一些特定场景,比如创建自定义的视图布局或实现某些特殊效果。然而,由于Android设备的多样性和不同屏幕尺寸的存在,使用绝对坐标来布局视图可能会导致在不同设备上显示效果的不一致,可能会出现重叠、截断或遮挡的情况。

属性名称功能
android:layout_x指定控件的左上角的x坐标
android:layout_y指定控件的左上角的y坐标
android:layout_width具体值(如100dp)、match_parent、wrap_content
android:layout_height具体值(如100dp)、match_parent、wrap_content

可以与下述的对齐属性一起使用:

属性名称功能
android:layout_alignParentToptrue/false,将控件相对于父布局的顶部对齐
android:layout_alignParentBottomtrue/false,将控件相对于父布局的底部对齐
android:layout_alignParentLefttrue/false,将控件相对于父布局的左边对齐
android:layout_alignParentRighttrue/false,将控件相对于父布局的右边进行对齐
android:layout_alignTop将控件相对于其他控件的顶部对齐
android:layout_alignBottom将控件相对于其他控件的底部对齐
android:layout_alignLeft将控件相对于其他控件的左边对齐
android:layout_alignRight将控件相对于其他控件的右边对齐
android:layout_centerHorizontal将控件在水平方向上居中对齐
android:layout_centerVertical将控件在垂直方向上居中对齐

AbsoluteLayout已被弃用,并不推荐在Android应用程序开发中使用。

TableLayout(表格布局):

  • 用途:
    TableLayout 可以将子视图组织成表格形式,类似于 HTML 的表格布局。它包含多个 TableRow,每个 TableRow 包含多个子视图。

  • 属性:

属性名称功能
android:layout_column设置该控件显示位置,如android:Layout_column="1"表示第2个位置显示。
android:layout_span设置该控件占几行,默认是1行。
android:stretchColumns设置可被拉伸的列。
android:shrinkColumns设置可被收缩的列。
android:collapseColumns设置可被隐藏的列。

注意:列的宽度是由该列中最宽那个决定

  • 样例:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:stretchColumns="2"
    android:shrinkColumns="1"
    android:collapseColumns="0">
    <TableRow>
        <Button
            android:layout_column="0"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮1"
            android:textSize="30sp"
            />
        <Button
            android:layout_column="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮2"
            android:textSize="30sp"
            />
    </TableRow>
    <TableRow>
        <Button
            android:layout_column="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮3"
            android:textSize="30sp"
            />
        <Button
            android:layout_column="2"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮4"
            android:textSize="30sp"
            />
    </TableRow>
    <TableRow>

        <Button
            android:layout_column="2"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮5"
            android:textSize="30sp"
            />
    </TableRow>
    <TableRow>
        <Button
            android:layout_column="0"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮6"
            android:textSize="30sp"
            />
        <Button
            android:layout_column="2"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="按钮7"
            android:textSize="30sp"
            />
    </TableRow>
</TableLayout>

GridLayout(网格布局):

  • 用途:
    GridLayout 将子视图组织成网格形式,类似于表格布局。
    可以通过 android:layout_row 和android:layout_column 属性指定子视图的行和列位置。

  • 属性:

属性名称功能
android:alignmentMode使视图的外边界之间进行校准。可以取以下值:
alignBounds – 对齐子视图边界。
alignMargins – 对齐子视图边距。
android:layout_gravity用来设置该 View 相对与父 View 的位置
android:orientation设置布局内控件的排列顺序
android:columnCount设置列数
android:rowCount设置行数
android:layout_columnSpan横跨几列
android:layout_rowSpan横跨几行
android:layout_column第几列
android:layout_row第几行
  • 样例:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:columnCount="4"
    android:orientation="horizontal"
    android:layout_marginHorizontal="0dp"
    android:rowCount="6" >

    <TextView
        android:layout_columnSpan="4"
        android:layout_gravity="fill"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:background="#D5DEDF"
        android:text="0"
        android:textSize="50sp" />

    <Button
        android:layout_columnSpan="2"
        android:layout_gravity="fill"
        android:text="回退" />

    <Button
        android:layout_columnSpan="2"
        android:layout_gravity="fill"
        android:text="清空" />

    <Button android:text="+" />

    <Button android:text="1" />

    <Button android:text="2" />

    <Button android:text="3" />

    <Button android:text="-" />

    <Button android:text="4" />

    <Button android:text="5" />

    <Button android:text="6" />

    <Button android:text="*" />

    <Button android:text="7" />

    <Button android:text="8" />

    <Button android:text="9" />

    <Button android:text="/" />

    <Button android:layout_width="wrap_content" android:text="." />

    <Button android:text="0" />

    <Button android:text="=" />
</GridLayout>

ScrollView(滚动布局):

  • 用途:
    ScrollView 是 Android 平台上的一个可滚动视图容器,它用于在一个可滚动区域内显示大量内容。当布局超过屏幕大小时,ScrollView 会自动启用滚动功能,用户可以通过滑动屏幕来查看隐藏部分的内容。

    在 ScrollView 中,只能包含一个直接子视图(ViewGroup),通常是一个垂直方向的线性布局或相对布局。如果需要水平滚动效果,可以使用 HorizontalScrollView 作为替代。

  • 属性:

属性名称功能
android:fillViewport用于指定内容是否填充 ScrollView 的视口。设置为 true 表示内容将充满整个ScrollView,默认为 false。
android:scrollbars定义滚动条的显示方式。可选值有"none"(不显示)、“vertical”(只显示垂直滚动条)和"horizontal"(只显示水平滚动条)。
android:scrollbarStyle指定滚动条的样式。可选值有"default"(默认样式)、“insideOverlay”(覆盖在内容上方)和"outsideOverlay"(位于内容旁边)。
android:fadeScrollbars控制滚动条是否在不活动状态时渐隐。设置为true表示滚动条会渐隐,默认为false。
  • 方法:
方法名称功能
scrollTo(int x, int y)将 ScrollView 滚动到指定的位置,参数x和y分别代表目标位置的水平和垂直偏移量。
fullScroll(int direction)使 ScrollView 滚动到指定的边界,参数 direction 可以是 View.FOCUS_UP(滚动到顶部)或 View.FOCUS_DOWN(滚动到底部)。
smoothScrollTo(int x, int y)平滑地将ScrollView滚动到指定的位置,会有滚动动画效果。
smoothScrollBy(int dx, int dy)平滑地将ScrollView滚动指定的偏移量,会有滚动动画效果。
isSmoothScrollingEnabled()判断平滑滚动是否启用。
  • 样例:
<ScrollView
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:fadeScrollbars="false">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">

       <TextView
           android:layout_width="match_parent"
           android:layout_height="40dp"
           android:gravity="center"
           android:background="@color/sky"
           android:text="这是 ScrollView 中第一行文本。" />

       <TextView
           android:layout_width="match_parent"
           android:layout_height="1000dp"
           android:gravity="center"
           android:background="@color/teal_700"
           android:text="这是 ScrollView 中间一个占用空间很大的视图。" />

       <TextView
           android:layout_width="match_parent"
           android:layout_height="40dp"
           android:gravity="center"
           android:background="@color/cyan"
           android:text="这是 ScrollView 中最后一行文本。" />
   </LinearLayout>
</ScrollView>

CoordinatorLayout(协调布局):

  • 用途:
    CoordinatorLayout 是用于处理子视图之间的协调动作的特殊布局。它常用于实现一些复杂的交互效果,如滚动时的视图协调。通过为 CoordinatorLayout 的子 View 设置 Behavior,可以实现不同的交互效果。通常会与 AppbarLayout、CollapsingToolbarLayout 结合使用。
    通常如果界面需要实现一个顶部优先折叠/出现的视图,可以使用 CoordinatorLayout 结合 Behavior 或者 AppbarLayout、CollapsingToolbarLayout 使用。
    此处不展开讨论具体使用方法。

自定义布局:

  • 用途:
    上面介绍了很多 Android 系统提供的布局方式,其实我们也可以自己动手实现一个自定义布局。
    可以根据一些自己项目的需要,定制一些特殊的布局方式,方便日常开发中复用。

  • 样例:
    下面实现一个类似上面 ConstraintLayout 中 Flow 的流式布局的自定义布局,暂时命名为 FlowLayoutM,暂时只支持子视图设置 Margin,直接使用系统的 MarginLayoutParams(需要自己添加支持的布局属性,以及子视图添加支持的布局属性,可以参照 FrameLayout 的实现)。
    新建一个类 FlowLayoutM 继承自 ViewGroup。重点重写其中的 onMeasure() 和 onLayout() 方法。
    onMeasure() 中计算所有 childView 的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是 wrap_content,直接使用父 ViewGroup 传入的计算值即可。)
    onLayout() 中对所有的 childView 进行布局。

FlowLayoutM 代码:

package com.example.layoutstudy;

import android.view.ViewGroup;
import java.util.ArrayList;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.graphics.Canvas;

public class FlowLayoutM extends ViewGroup {
    public int horizontalSpacing; // 水平间距
    public int verticalSpacing; // 竖直间距
    public ArrayList<Line> lineList;
    public FlowLayoutM(Context context) {
        super(context,null);
    }
    public FlowLayoutM(Context context, AttributeSet attrs) {
        super(context, attrs,0);
    }
    public FlowLayoutM(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public void setHorizontalSpacing(int horizontalSpacing) {
        this.horizontalSpacing = horizontalSpacing;
        this.requestLayout();
    }
    public void setVerticalSpacing(int verticalSpacing) {
        this.verticalSpacing = verticalSpacing;
        this.requestLayout();
    }

    /**
     * 分行:遍历所有的子View,判断哪几个子View在同一行(排座位表)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (lineList != null) {
            lineList.clear();
        }
        lineList = new ArrayList<>();
        System.out.println("================onMeasure()方法");

        // 1.FlowLayout 的宽度,也就是申请宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        // 2.获取用于实际比较的宽度,就是除去2边的padding的宽度
        int noPaddingWidth = width - getPaddingLeft() - getPaddingRight();

        // 3.遍历所有的子View,拿子View的宽和noPaddingWidth进行比较
        Line line = new Line();
        for (int i = 0; i < getChildCount();i++) {
            View subView = getChildAt(i);
            // 保证能够获取到宽高
            subView.measure(0,0);

            if (line.getViewList().size() == 0) {
                // 4.如果当前line中没有子View,则不用比较直接放入line中,因为要保证每行至少有一个子View;
                line.addLineView(subView);
            } else if (line.getLineWidth() + horizontalSpacing + subView.getMeasuredWidth() > noPaddingWidth) {
                // 5.如果当前line的宽+水平间距+子View的宽大于noPaddingWidth,则child需要换行
                lineList.add(line);
                line = new Line();
                line.addLineView(subView);
            } else {
                line.addLineView(subView);
            }
            if (i == getChildCount() - 1) {
                // 6.如果当前child是最后的子View,那么需要保存最后的line对象
                lineList.add(line);
            }
        }

        int height = getPaddingTop() + getPaddingBottom(); // 申请高度
        for (int i = 0; i < lineList.size(); i++) {
            height += lineList.get(i).getLineHeight();
        }
        height += (lineList.size() - 1) * verticalSpacing;

        // 7.设置当前控件的宽高,或者向父 view 申请宽高
        setMeasuredDimension(width, height);

        System.out.println("======width=" + width + ",height=" + height);
    }

    /**
     * 让每个子视图递归 layout
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        System.out.println("================onLayout()方法");
        int paddingTop = getPaddingTop();
        int paddingLeft = getPaddingLeft();
        for (int i = 0; i < lineList.size(); i++) {
            // 1.获取line的view的集合
            Line line = lineList.get(i);
            if (i > 0) {
                // 从第二行要加行距
                paddingTop += verticalSpacing + lineList.get(i - 1).getLineHeight();
            }
            ArrayList<View> viewList = line.getViewList();
            int reMainSpacing = getReMainSpacing(line); // 计算空白空间,等分给各个子视图
            int spacing = reMainSpacing / viewList.size();
            for (int j = 0; j < viewList.size(); j++) { // 2.获取每一行的子view的集合
                View view = viewList.get(j);
                int specWidth = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth() + spacing, MeasureSpec.EXACTLY);
                view.measure(specWidth, MeasureSpec.UNSPECIFIED); // 保证能够获取到宽高
                if (j == 0) {
                    // 如果是第一个子view就放在左边就可以了
                    view.layout(paddingLeft, paddingTop,paddingLeft + view.getMeasuredWidth(),paddingTop + view.getMeasuredHeight());
                } else {
                    View viewLast = viewList.get(j - 1);
                    int left = viewLast.getRight() + horizontalSpacing;
                    view.layout(left, viewLast.getTop(),left + view.getMeasuredWidth(), viewLast.getBottom());
                }
            }
        }
    }

    /**
     * 获取每行空白的宽度
     */
    public int getReMainSpacing(Line line) {
        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - line.getLineWidth(); // 测量的宽分别减去...
    }

    /**
     * 封装每行的数据
     */
    class Line {
        int width;
        int height;
        ArrayList<View> viewList;

        public Line() {
            viewList = new ArrayList<>();
        }

        /**
         * 换行的方法
         */
        public void addLineView(View child) {
            if (!viewList.contains(child)) {
                viewList.add(child);
                if (viewList.size() == 1) {
                    width = child.getMeasuredWidth();
                } else {
                    width += child.getMeasuredWidth() + horizontalSpacing;
                }
            }
            height = Math.max(height, child.getMeasuredHeight());
        }
        /**
         * 获取当前行的宽度
         * @return
         */
        public int getLineWidth() {
            return width;
        }

        /**
         * 获取当前行的高度
         * @return
         */
        public int getLineHeight() {
            return height;
        }

        /**
         * 获取当前行的所有子view
         * @return
         */
        public ArrayList<View> getViewList() {
            return viewList;
        }
    }
}

Activity 中代码:

public class LayoutStudyActivity extends AppCompatActivity {
    private Context context;
    private int dimens;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_layout_study);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        context = this;
        FlowLayoutM flowLayout = (FlowLayoutM)findViewById(R.id.flow_layout_01);
        FlowLayoutM flowLayout2 = (FlowLayoutM)findViewById(R.id.flow_layout_02);
        FlowLayoutM flowLayout3 = (FlowLayoutM)findViewById(R.id.flow_layout_03);
        dimens = getDimens(R.dimen.common10dp);
        flowLayout.setVerticalSpacing(dimens);
        flowLayout.setHorizontalSpacing(dimens);
        flowLayout.setPadding(dimens, dimens, dimens, dimens);

        flowLayout2.setVerticalSpacing(dimens);
        flowLayout2.setHorizontalSpacing(dimens);
        flowLayout2.setPadding(dimens, dimens, dimens, dimens);

        flowLayout3.setVerticalSpacing(dimens);
        flowLayout3.setHorizontalSpacing(dimens);
        flowLayout3.setPadding(dimens, dimens, dimens, dimens);

        final ArrayList<String> list = getToAddStringList();
        if (list != null) {
            for (int g = 0; g < list.size(); g++) {
                Button tv = new Button(LayoutStudyActivity.this);
                tv.setText(list.get(g));
                tv.setTextColor(Color.CYAN);
                tv.setTextSize(16);
                tv.setGravity(Gravity.CENTER);
                tv.setPadding(dimens, dimens, dimens, dimens);
                Drawable pressed = createDrawable(getRadmoColor(), dimens);
                Drawable normal = createDrawable(getRadmoColor(), dimens);
                tv.setBackgroundDrawable(createSelector(pressed, normal)); //设置按下的颜色
                flowLayout.addView(tv);
                final int finalG = g;
                tv.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //Toast.makeText(LayoutStudyActivity.this, list.get(finalG).toString(), Toast.LENGTH_SHORT).show();
                        System.out.println("您点击了标签:" + list.get(finalG).toString());
                    }
                });
            }

            flowLayout.requestLayout();
        }
    }

    public static int getRadmoColor() {
        Random random = new Random();
        return Color.rgb(random.nextInt(150), random.nextInt(150), random.nextInt(150));
    }

    public static Drawable createSelector(Drawable pressed, Drawable normal) {
        StateListDrawable drawable = new StateListDrawable();
        // 添加按压状态以及图图片
        drawable.addState(new int[]{android.R.attr.state_pressed}, pressed);
        // 添加正常状态以及图片
        drawable.addState(new int[]{}, normal);

        // 设置状态选择器的过渡动画,让其平滑的执行
        drawable.setEnterFadeDuration(500);
        drawable.setExitFadeDuration(500);

        return drawable;
    }

    public static Drawable createDrawable(int color, float radius) {
        GradientDrawable drawable = new GradientDrawable();
        drawable.setShape(GradientDrawable.RECTANGLE); // 设置形状为圆角矩形
        drawable.setColor(color); // 设置颜色
        drawable.setCornerRadius(radius); // 设置角度
        return drawable;
    }

    public static ArrayList<String> getToAddStringList() {
        ArrayList<String> list = new ArrayList<>();
        String[] labelTexts = new String[]{ "      代码添加标签1      ", "代码添加标签2", "代码添加标签3" }; // 设置数据
        for (int i = 0; i < labelTexts.length; i++) {
            list.add(labelTexts[i]);
        }
        return list;
    }

    public int getDimens(int id) {
        return context.getResources().getDimensionPixelSize(id);
    }
}

布局文件中代码:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="50dp"
    android:background="@color/teal_700"
    android:orientation="vertical"
    >

    <com.example.layoutstudy.FlowLayoutM
        android:id="@+id/flow_layout_01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            style="@style/text_flag_01"
            android:text="线性布局" />

        <TextView
            style="@style/text_flag_01"
            android:text="相对布局" />

        <TextView
            style="@style/text_flag_01"
            android:text="帧布局" />

        <TextView
            style="@style/text_flag_01"
            android:text="约束布局" />

        <TextView
            style="@style/text_flag_01"
            android:text="表格布局" />

        <TextView
            style="@style/text_flag_01"
            android:text="网格布局" />

        <TextView
            style="@style/text_flag_01"
            android:text="自定义布局" />
    </com.example.layoutstudy.FlowLayoutM>

    <com.example.layoutstudy.FlowLayoutM
        android:id="@+id/flow_layout_02"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp" >

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="线性布局"
            android:textColor="#888888" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="相对布局"
            android:textColor="#888888" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="帧布局"
            android:textColor="#888888" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="约束布局"
            android:textColor="#888888" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="表格布局"
            android:textColor="#888888" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="网格布局"
            android:textColor="#888888" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_02"
            android:text="自定义布局"
            android:textColor="#888888" />
    </com.example.layoutstudy.FlowLayoutM>

    <com.example.layoutstudy.FlowLayoutM
        android:id="@+id/flow_layout_03"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp" >

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="线性布局"
            android:textColor="#43BBE7" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="相对布局"
            android:textColor="#43BBE7" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="帧布局"
            android:textColor="#43BBE7" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="约束布局"
            android:textColor="#43BBE7" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="表格布局"
            android:textColor="#43BBE7" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="网格布局"
            android:textColor="#43BBE7" />

        <TextView
            style="@style/text_flag_01"
            android:background="@drawable/flag_03"
            android:text="自定义布局"
            android:textColor="#43BBE7" />
    </com.example.layoutstudy.FlowLayoutM>
</LinearLayout>

相关边框及样式:

flag_01.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#7690A5"></solid>
    <corners android:radius="5dp"/>
    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />
</shape>


flag_02.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FFFFFF"></solid>
    <corners android:radius="40dp"/>
    <stroke android:color="#C9C9C9" android:width="2dp"/>
    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />
</shape>


flag_03.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FFFFFF" ></solid>
    <corners android:radius="40dp"/>
    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />
</shape>


styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="text_flag_01">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">4dp</item>
        <item name="android:background">@drawable/flag_01</item>
        <item name="android:textColor">#ffffff</item>
    </style>
</resources>

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值