# 容器断点 (ContainerReader)

容器断点组件ContainerReader是ArkUI提供的一种响应式布局解决方案，从API版本26.0.0开始，允许开发者基于容器尺寸而非窗口尺寸实现自适应布局。与传统的窗口断点相比，容器断点提供了更细粒度的布局控制能力，使得组件能够在不同的容器尺寸下呈现不同的布局效果。

ContainerReader在实际开发中常用于Flex、Row或Column容器内部、Navigation以及自定义组件内部场景，提供实时尺寸获取、断点值获取和自定义断点阈值核心功能。

#### 能力范围

ContainerReader提供以下关键能力。

*   **容器级尺寸感知**：基于组件自身实际尺寸确定断点值，而非窗口尺寸。适用于组件级响应式布局、嵌套布局场景和可复用组件开发。
*   **双向绑定实时获取**：通过状态变量实时获取容器尺寸和断点信息。
*   **宽度/高度双模式**：同时支持宽度断点和高度断点，满足不同维度的自适应需求。
*   **自定义断点阈值**：通过breakpointConfig属性灵活配置不同尺寸区间对应的布局策略。

#### 布局规格

从API版本26.0.0开始，ContainerReader组件在不同父容器类型下的布局规格如下。

*   当父容器为Flex组件、Row组件或Column组件时，ContainerReader会占满父容器剩余空间，Flex的弹性特性生效优先级不变。
*   当父容器为其他容器类型时，ContainerReader会撑满父容器。
*   ContainerReader的尺寸由父容器和自身布局确定，不受子组件影响。

| 父容器类型 | 无兄弟节点 | 有兄弟节点 |
| --- | --- | --- |
| Flex、Row、Column | 撑满父容器。 | 撑满父容器剩余空间。 |
| 其他类型组件 | 撑满父组件。 | 撑满父组件。 |

ContainerReader作为子组件时，其尺寸由父容器决定。当父容器为Flex、Row或Column时，ContainerReader会根据父容器的布局方向自动撑满父容器剩余空间。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';
@Entry
@Component
struct Example {
  @State containerSize: Size = { width: 0, height: 0 };
  @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  build() {
    Flex({ direction: FlexDirection.Row }) {
      ContainerReader({
        size: this.containerSize!!,
        widthBreakpoint: this.widthBp!!
      }) {
        Column() {
          Text('Adaptive Content')
        }
        .width('100%')
        .height('100%')
      }
      .backgroundColor('#F7F7F7')
    }
    .padding(10)
    .width('100%')
    .height(200)
    .backgroundColor('#D5D5D5')
  }
}
```

ContainerReader作为Flex、Row或Column的子组件使用时，会优先为非ContainerReader类型的子组件测算尺寸，再结合父容器剩余空间与开发者设置为ContainerReader组件分配空间。这在固定内容与自适应内容并存的场景中较为适用。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';
@Entry
@Component
struct Example {
  @State containerSize: Size = { width: 0, height: 0 };
  @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  build() {
    Flex({ direction: FlexDirection.Row }) {
      Column() {
        Text('Text')
          .width(100)
          .height('100%')
      }
      .width(100)
      .height('100%')
      .backgroundColor('#D5D5D5')
      ContainerReader({
        size: this.containerSize!!,
        widthBreakpoint: this.widthBp!!
      }) {
        Column() {
          Text('ContainerReader')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      .backgroundColor('#F7F7F7')
    }
    .width('100%')
    .height(300)
    .backgroundColor('#F0FAFF')
    .padding(10)
  }
}
```

当Flex、Row或Column容器中有多个ContainerReader子组件时，按开发者书写顺序第一个ContainerReader会占满剩余空间，此时其余ContainerReader组件的主轴大小为0。但开发者可以通过layoutWeight属性使多个ContainerReader平分剩余空间。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';
@Entry
@Component
struct Example {
  @State containerSize: Size = { width: 0, height: 0 };
  @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  @State containerSize1: Size = { width: 0, height: 0 };
  @State widthBp1: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  build() {
    Flex({ direction: FlexDirection.Row }) {
      // 固定宽度的兄弟组件，优先测算
      Column() {
        Text('Text')
          .width(80)
          .height('100%')

      }
      .width(80)
      .height('100%')
      .backgroundColor('#D5D5D5')

      // 第一个ContainerReader，通过layoutWeight(1)分配1/2剩余空间
      ContainerReader({
        size: this.containerSize!!,
        widthBreakpoint: this.widthBp!!
      }) {
        Column() {
          Text('ContainerReader1')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      .layoutWeight(1)
      .backgroundColor('#F7F7F7')

      // 第二个ContainerReader，同样layoutWeight(1)分配1/2剩余空间
      ContainerReader({
        size: this.containerSize1!!,
        widthBreakpoint: this.widthBp1!!
      }) {
        Column() {
          Text('ContainerReader2')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      .layoutWeight(1)
      .backgroundColor('#707070')
    }
    .width('100%')
    .height(300)
    .backgroundColor('#F0FAFF')
    .padding(10)
  }
}
```

#### 约束与限制

**状态变量要求**

ContainerReaderInfo的所有参数都必须通过状态变量进行双向绑定。

```
// 正确用法 - 使用!!触发双向绑定
@State containerSize: Size = { width: 0, height: 0 };
@State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;

ContainerReader({
  size: this.containerSize!!,
  widthBreakpoint: this.widthBp!!
})

// 错误用法1 - 未使用!!后缀，双向绑定不生效
ContainerReader({
  size: this.containerSize,
  widthBreakpoint: this.widthBp
})

// 错误用法2 - 使用非状态变量
const containerSize: Size = { width: 0, height: 0 };

ContainerReader({
  size: containerSize,  // 必须是@State修饰的状态变量
  widthBreakpoint: WidthBreakpoint.WIDTH_MD
})
```

**尺寸计算时机**

ContainerReader的尺寸由父容器和自身布局确定，不受子组件影响。在ContainerReader组件尺寸测算时，先根据父组件和自身设置确认自身尺寸，然后再做子组件的展开尺寸测算。为防止在节点未尺寸测算前的生命周期中使用ContainerReader组件双向绑定的size状态变量，需要给双向绑定状态变量一个初始值。

因此请确保，父容器有明确尺寸，含有ContainerReader组件的父容器不应依赖子节点确认自身大小，ContainerReader组件不依赖自身子节点大小确认尺寸。

#### 获取ContainerReader容器尺寸

ContainerReader的主要接口包括ContainerReader和breakpointConfig。

*   ContainerReader接口
    
    ContainerReader是实现容器断点的核心组件，其使用要求如下。ContainerReaderInfo所有参数都必须通过状态变量进行双向绑定。ContainerReader通过双向绑定机制，将后端计算的尺寸和断点值实时更新到状态变量中。不能通过改变此处ContainerReaderInfo的size来试图设置ContainerReader尺寸。在使用状态变量时需要添加!!后缀，触发双向绑定更新。
    
*   breakpointConfig属性
    
    通过breakpointConfig属性可以自定义断点阈值。断点数组必须为单调递增数组。其中，宽度断点最多支持5个，即数组最大长度为4；高度断点最多支持3个，即数组最大长度为2。断点区间为左闭右开区间\[breakpoint\[i\], breakpoint\[i+1\])。宽度断点值单位为vp；高度断点值为组件高度与宽度的比值，无单位。
    
    **异常处理规则：**
    
    | 异常情况 | 处理方式 |
    | --- | --- |
    | 数组大小超过最大数量。 | 使用系统默认断点。 |
    | 数组非递增。 | 取递增结束的子数组。 |
    | 数组中存在异常值（非数字等）。 | 跳过异常值，只处理有效值。 |
    
    **示例：**
    
    ```
    // 示例1：数组超过最大长度[320, 600, 840, 1440, 2000, 3000]
    // 超过部分被忽略，使用系统默认：[320, 600, 840, 1440]
    .breakpointConfig({ width: [320, 600, 840, 1440, 2000, 3000] })
    
    // 示例2：数组非递增[100, 50, 300, 400]
    // 取递增结束的子数组：[100]
    .breakpointConfig({ width: [100, 50, 300, 400] })
    
    // 示例3：数组包含异常值[100, undefined, 300, 400]
    // 跳过异常值undefined，处理有效值：[100, 300, 400]
    .breakpointConfig({ width: [100, undefined, 300, 400] })
    ```
    

下面介绍容器断点的简单开发步骤。

1.  声明状态变量。
    
    首先需要声明用于存储容器尺寸和断点信息的状态变量并初始化，防止在未获取ContainerReader的大小和断点时使用造成异常。
    
    ```
    @State containerSize: Size = { width: 0, height: 0 };
    @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
    ```
    
2.  配置ContainerReader。
    
    将状态变量绑定到ContainerReader组件，使用!!后缀触发双向绑定更新。
    
    ```
    ContainerReader({
      size: this.containerSize!!,
      widthBreakpoint: this.widthBp!!
    }) {
      Column() {
        Text('Adaptive Content')
      }
      .width('100%')
      .height('100%')
    }
    ```
    
3.  确定容器尺寸。
    
    情况一：ContainerReader不显式设置尺寸，它通过以下方式自动获取尺寸。
    
    *   **撑满父容器**：当ContainerReader是父容器的唯一子组件时，自动撑满父容器。
    *   **撑满剩余空间**：当父容器Flex、Row或Column有非ContainerReader类型的子组件时，ContainerReader占满剩余空间。
    *   **按比例分配剩余空间**：当父容器Flex、Row或Column有多个ContainerReader子组件时，ContainerReader通过layoutWeight分配剩余空间。
    
    情况二：可以为ContainerReader设置布局约束等尺寸属性来约束其尺寸。当Flex、Row或Column容器中有多个ContainerReader子组件时，一般情况下按开发者书写顺序第一个ContainerReader会占满剩余空间，此时其余ContainerReader组件的主轴大小为0。当开发者给书写顺序靠前的ContainerReader设置了尺寸约束时，会按尺寸约束分配给ContainerReader空间，再将剩余空间分配给开发者写的下一个ContainerReader。
    
    以情况一的撑满父容器为例，Flex给子组件ContainerReader分配与Flex等大的空间。
    
    使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。
    
    ```
    import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';
    @Entry
    @Component
    struct Example {
      @State containerSize: Size = { width: 0, height: 0 };
      @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
      build() {
        Flex({ direction: FlexDirection.Row }) {
          ContainerReader({
            size: this.containerSize!!,
            widthBreakpoint: this.widthBp!!
          }) {
            Column() {
              Text('Adaptive Content')
            }
            .width('100%')
            .height('100%')
          }
          .backgroundColor('#F7F7F7')
        }
        .padding(10)
        .width('100%')
        .height(200)
        .backgroundColor('#D5D5D5')
      }
    }
    ```
    

#### 实现独立断点

在同一组件中，可以同时使用多个ContainerReader组件，每个组件可以拥有独立的断点状态，实现更精细化的布局控制。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';
@Entry
@Component
struct MultiContainerExample {
  // 左右两个ContainerReader各自拥有独立的状态变量，互不影响
  @State leftContainerSize: Size = { width: 0, height: 0 };
  @State rightContainerSize: Size = { width: 0, height: 0 };
  @State leftWidthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  @State rightWidthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;

  build() {
    Row({ space: 10 }) {
      // 左侧容器，独立感知自身尺寸和断点
      Flex({ direction: FlexDirection.Column }) {
        ContainerReader({
          size: this.leftContainerSize!!,
          widthBreakpoint: this.leftWidthBp!!
        }) {
          Column() {
            Text('Left Container')
          }
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#F0FAFF')
      }
      .layoutWeight(1)
      .height(300)

      // 右侧容器，独立感知自身尺寸和断点
      Flex({ direction: FlexDirection.Column }) {
        ContainerReader({
          size: this.rightContainerSize!!,
          widthBreakpoint: this.rightWidthBp!!
        }) {
          Column() {
            Text('Right Container')
          }
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#D5D5D5')
      }
      .layoutWeight(1)
      .height(300)
    }
    .width('100%')
    .padding(20)
  }
}
```

#### 网格组件根据自身容器断点设置列数

在多设备开发中，网格组件（如Grid）可以根据自身容器尺寸设置不同的列数，实现自适应的列表布局。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';
@Entry
@Component
struct GridBreakpointExample {
  @State containerSize: Size = { width: 0, height: 0 };
  @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  @State bgColors: ResourceColor[] =
    ['#707070', '#004AAF', '#2787D9', '#F7F7F7', '#F0FAFF'];

  build() {
    Column({ space: 10 }) {
      Text('Grid with Container Breakpoint')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      // ContainerReader包裹Grid，根据容器宽度断点动态调整列数
      Flex({ direction: FlexDirection.Column }) {
        ContainerReader({
          size: this.containerSize!!,
          widthBreakpoint: this.widthBp!!
        }) {
          Grid() {
            ForEach(this.bgColors, (color: ResourceColor, index?: number) => {
              GridItem() {
                Row() {
                  Text(`${index}`)
                }
                .width('100%')
                .height('50vp')
                .justifyContent(FlexAlign.Center)
              }
              .backgroundColor(color)
            })
          }
          // 根据断点值动态设置列模板
          .columnsTemplate(this.getColumnsTemplate())
          .columnsGap(10)
          .rowsGap(10)
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height(300)
        .backgroundColor('#FFFFFF')
      }
      .width('80%')
      .height(320)
      .padding(10)
      .backgroundColor('#D5D5D5')
      .border({ width: 1, color: '#D5D5D5' })
    }
    .width('100%')
    .padding(20)
  }

  // 根据宽度断点返回对应的列模板
  getColumnsTemplate(): string {
    if (this.widthBp === WidthBreakpoint.WIDTH_XS) {
      return '1fr';
    } else if (this.widthBp === WidthBreakpoint.WIDTH_SM) {
      return '1fr 1fr';
    } else if (this.widthBp === WidthBreakpoint.WIDTH_MD) {
      return '1fr 1fr 1fr';
    } else {
      return '1fr 1fr 1fr 1fr';
    }
  }
}
```

#### 自定义组件根据容器断点自适应布局

开发者可以创建自定义组件，组件内部使用ContainerReader来实现自适应的内部布局，使组件在不同的使用场景下都能呈现最佳效果。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import {ContainerReader, ContainerReaderAttribute, Size} from '@kit.ArkUI';

// 自适应卡片组件，内部使用ContainerReader感知容器尺寸
@Component
struct AdaptiveCard {
  @State containerSize: Size = { width: 0, height: 0 };
  @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  @Prop title: string = 'Card Title';
  @Prop content: string = 'Card content text';

  build() {
    ContainerReader({
      size: this.containerSize!!,
      widthBreakpoint: this.widthBp!!
    }) {
      Text('width' + this.containerSize?.width)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#D5D5D5')
    .border({ width: 1, color: '#D5D5D5', radius: 8 })
  }
}

// 使用不同尺寸的容器展示AdaptiveCard的自适应效果
@Entry
@Component
struct AdaptiveCardExample {
  build() {
    Column({ space: 20 }) {
      Text('Adaptive Card Components')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Text('Small Container:')
        .fontSize(14)
        .fontColor('#707070')
        .alignSelf(ItemAlign.Start)

      AdaptiveCard({
        title: 'Small Card',
        content: 'This card adapts to small containers with vertical layout.'
      })
        .width(200)
        .height(200)

      Text('Large Container:')
        .fontSize(14)
        .fontColor('#707070')
        .alignSelf(ItemAlign.Start)

      AdaptiveCard({
        title: 'Large Card',
        content: 'This card adapts to large containers with horizontal layout.'
      })
        .width(300)
        .height(150)
    }
    .width('100%')
    .padding(20)
  }
}
```

#### 左右分栏布局自适应

在主从结构页面中，左侧为固定宽度的Tab标签，右侧为自适应的详情区域。右侧详情区域使用ContainerReader，根据剩余宽度自动切换布局：窄屏时上下排列，宽屏时左右排列。

使用ContainerReader需要同时导入ContainerReaderAttribute，否则会导致编译报错。

```
import { ContainerReader, ContainerReaderAttribute, Size } from '@kit.ArkUI';

@Entry
@Component
struct SplitLayoutExample {
  @State containerSize: Size = { width: 0, height: 0 };
  @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  @State currentTabIndex: number = 0;
  private menuItems: string[] = ['选项一', '选项二', '选项三', '选项四'];

  build() {
    Column() {
      Text('分栏布局')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      Flex({ direction: FlexDirection.Row }) {
        // 左侧：垂直方向的Tabs标签栏，固定宽度100
        Tabs({ barPosition: BarPosition.Start, index: this.currentTabIndex }) {
          ForEach(this.menuItems, (item: string, index?: number) => {
            TabContent() {
              // 右侧：ContainerReader感知TabContent区域宽度
              ContainerReader({
                size: this.containerSize!!,
                widthBreakpoint: this.widthBp!!
              }) {
                this.buildDetailContent()
              }
              .padding(10)
            }
            .tabBar(item)
          }, (item: string, index?: number) => `${index}`)
        }
        .onChange((index: number) => {
          this.currentTabIndex = index;
        })
        .vertical(true)
        .barWidth(100)
        .backgroundColor('#F7F7F7')
      }
      .width('100%')
      .height(300)
    }
    .width('100%')
    .padding(20)
  }

  // 根据断点切换详情区域布局
  @Builder
  buildDetailContent() {
    if (this.widthBp === WidthBreakpoint.WIDTH_XS || this.widthBp === WidthBreakpoint.WIDTH_SM) {
      // 窄屏：标题和内容上下排列
      Column({ space: 10 }) {
        Column() {
          Text('标题区域')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
        }
        .width('100%')
        .height(60)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)

        Column() {
          Text('内容区域')
            .fontSize(14)
        }
        .width('100%')
        .layoutWeight(1)
        .backgroundColor('#F0FAFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')
      .height('100%')
    } else {
      // 宽屏：标题和内容左右排列
      Row({ space: 10 }) {
        Column() {
          Text('标题区域')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
        }
        .width(120)
        .height('100%')
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)

        Column() {
          Text('内容区域')
            .fontSize(14)
        }
        .layoutWeight(1)
        .height('100%')
        .backgroundColor('#F0FAFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')
      .height('100%')
    }
  }
}
```

窄屏时上下排列。

宽屏时左右排列。