美文网首页
安卓开发中各种布局方式总结

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

作者: BenXia | 来源:发表于2024-04-06 00:59 被阅读0次

    通用布局属性

    属性名称 功能
    android:id 设置布局的标识
    android:layout_width 设置布局的宽度
    android:layout_height 设置布局的高度
    android:layout_margin 设置当前布局与屏幕边界或与周围控件的距离
    android:padding 设置当前布局与该布局中控件的距离
    android:background 设置布局的背景
    android:visibility visible:显示;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|bottom)。
      写在父组件里,控制子组件。
      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>
    
    layout_weight 属性

    给其中的按钮 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"
            />
    
    layout_margin 与 padding

    这里可以看到 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">
    
    background 设置边框样式

    另一个展示 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>
    
    layout_gravity、layout_weight

    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>
    
    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>
    
    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>
    
    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>
    
    PercentFrameLayout、layout_gravity

    仔细对比会看到中间的按钮位置往右往下移动了。
    上述各种 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>
    
    0dp 约束决定

    给上面的一些视图添加 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>
    
    padding 和 margin 对布局的影响

    可以发现:
    上述与父视图对齐相关(当前视图的包含 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>
    
    layout_constraintHorizontal_weight、layout_width="0dp"
    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>
    
    layout_constraintHorizontal/Vertical_bias

    上述与父视图对齐相关(当前视图的包含 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>
    
    layout_constraintWidth/Height_percent
    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>
    
    layout_constraintDimensionRatio
    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>
    
    goneMargin
    1. wrap_content 下的 layout_constrainedWidth/Height 强制约束
    属性名称 功能
    app:layout_constrainedWidth true/false,width 为 wrap_content 时仍需满足约束
    app:layout_constrainedHeight true/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>
    
    wrap_content、layout_constrainedWidth
    1. 链式布局(chain)
    属性名称 功能
    app:layout_constraintHorizontal_chainStyle packed、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>
    
    chainStyle,layout_constraintHorizontal_weight, layout_width="0dp"

    右侧图片和文字,需要整体跟左边头像居中。
    某个元素使用 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>
    
    layout_constraintVertical_chainStyle
    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:orientation vertical/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>
    
    Guideline
    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>
    
    Barrier
    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>
    
    Group

    FlowLayout(流式布局):

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

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

    • 属性:

    属性名称 功能
    android:orientation horizontal、vertical,水平方向的流式,还是垂直方向。
    app:flow_wrapMode none(默认值):不换行
    chain:换行
    aligned:换行并且上下对齐
    app:flow_horizontalGap 横向行间距
    app:flow_verticalGap 纵向行间距
    app:flow_verticalAlign top、bottom、center、baseline,在 android:orientation 为 horizontal 时有效
    app:flow_horizontalAlign start、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_horizontalBias wrapMode 为 chian 时并且 style 为 packed 时有效
    app:flow_verticalBias wrapMode 为 chian 时并且 style 为 packed 时有效
    app:flow_firstHorizontalBias wrapMode 为 chian 时并且 style 为 packed 时有效
    app:flow_firstVerticalBias wrapMode 为 chian 时并且 style 为 packed 时有效
    app:flow_lastHorizontalBias wrapMode 为 chian 时并且 style 为 packed 时有效
    app:flow_lastVerticalBias wrapMode 为 chian 时并且 style 为 packed 时有效
    app:flow_maxElementsWrap 配置每行(horizontal)或者每列(vertical)最多的视图数量
    style、bias、weight

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

    flow_wrap 为 none

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

    flow_wrap 为 chain

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

    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>
    
    Flow

    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_alignParentTop true/false,将控件相对于父布局的顶部对齐
    android:layout_alignParentBottom true/false,将控件相对于父布局的底部对齐
    android:layout_alignParentLeft true/false,将控件相对于父布局的左边对齐
    android:layout_alignParentRight true/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>
    
    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>
    
    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>
    
    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>
    
    自定义流式布局

    相关文章

      网友评论

          本文标题:安卓开发中各种布局方式总结

          本文链接:https://www.haomeiwen.com/subject/vuogtjtx.html