目录

Container Breakpoint (ContainerReader)

The container breakpoint component ContainerReader is a responsive layout solution provided by ArkUI. Starting from API version 26.0.0, it allows you to implement adaptive layouts based on container size rather than window size. Compared with traditional window breakpoints, container breakpoints provide finer-grained layout control, enabling components to present different layout effects under different container sizes.

In actual development, ContainerReader is often used inside Flex, Row, or Column containers, Navigation, and custom components, providing core functions such as real-time size acquisition, breakpoint value retrieval, and custom breakpoint thresholds.

Capability Scope

ContainerReader provides the following key capabilities.

Layout Specifications

Starting from API version 26.0.0, the layout specifications of the ContainerReader component under different parent container types are as follows.

  • When the parent container is a Flex, Row, or Column component, ContainerReader fills the remaining space of the parent container, and the priority of Flex's flexibility features remains unchanged.
  • When the parent container is of another container type, ContainerReader fills the parent container.
  • The size of ContainerReader is determined by the parent container and its own layout, and is not affected by child components.
Parent Container Type No Sibling Nodes Has Sibling Nodes
Flex, Row, Column Fills the parent container. Fills the remaining space of the parent container.
Other component types Fills the parent component. Fills the parent component.

When ContainerReader is used as a child component, its size is determined by the parent container. When the parent container is Flex, Row, or Column, ContainerReader automatically fills the remaining space of the parent container along its layout direction.

import {ContainerReader,  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')
  }
}

When ContainerReader is used as a child component of Flex, Row, or Column, the system prioritizes size calculation for non-ContainerReader child components, and then allocates space to the ContainerReader component based on the remaining container space and your settings. This is particularly suitable for scenarios where fixed content and adaptive content coexist.

import {ContainerReader,  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)
  }
}

When there are multiple ContainerReader child components in a Flex, Row, or Column container, the first ContainerReader in your writing order will fill the remaining space, while the main axis size of the other ContainerReader components will be 0. However, you can use the layoutWeight attribute to make multiple ContainerReader components equally divide the remaining space.

import {ContainerReader,  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 }) {
      // Sibling component with fixed width, measured first
      Column() {
        Text('Text')
          .width(80)
          .height('100%')

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

      // First ContainerReader, allocated 1/2 of the remaining space via layoutWeight(1)
      ContainerReader({
        size: this.containerSize!!,
        widthBreakpoint: this.widthBp!!
      }) {
        Column() {
          Text('ContainerReader1')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      .layoutWeight(1)
      .backgroundColor('#F7F7F7')

      // Second ContainerReader, also allocated 1/2 of the remaining space via layoutWeight(1)
      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)
  }
}

Constraints

State variable requirements

All parameters of ContainerReaderInfo must be two-way bound via state variables.

// Correct usage - use !! to trigger two-way binding
@State containerSize: Size = { width: 0, height: 0 };
@State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;

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

// Incorrect usage 1 - !! suffix not used, two-way binding does not take effect
ContainerReader({
  size: this.containerSize,
  widthBreakpoint: this.widthBp
})

// Incorrect usage 2 - using a non-state variable
const containerSize: Size = { width: 0, height: 0 };

ContainerReader({
  size: containerSize,  // Must be a state variable decorated with @State
  widthBreakpoint: WidthBreakpoint.WIDTH_MD
})

Size calculation timing

The size of ContainerReader is determined by the parent container and its own layout, and is not affected by child components. When the ContainerReader component size is calculated, its own size is first confirmed based on the parent component and its own settings, and then the expanded size calculation of child components is performed. To prevent using the two-way bound state variable size of the ContainerReader component in the lifecycle before the node's size is calculated, an initial value must be assigned to the two-way bound state variable.

Therefore, ensure that the parent container has a definite size. The parent container containing the ContainerReader component should not rely on child nodes to determine its own size, and the ContainerReader component should not rely on the size of its own child nodes to determine its size.

Obtaining the ContainerReader Container Size

ContainerReader provides the ContainerReader API and breakpointConfig attribute.

The following describes the simple development steps for container breakpoints.

  1. Declare state variables.

    First, declare and initialize state variables for storing container size and breakpoint information to prevent exceptions caused by using them before the ContainerReader's size and breakpoints are obtained.

    @State containerSize: Size = { width: 0, height: 0 };
    @State widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_MD;
  2. Configure ContainerReader.

    Bind the state variables to the ContainerReader component, using the !! suffix to trigger two-way binding updates.

    ContainerReader({
      size: this.containerSize!!,
      widthBreakpoint: this.widthBp!!
    }) {
      Column() {
        Text('Adaptive Content')
      }
      .width('100%')
      .height('100%')
    }
  3. Determine the container size.

    Case 1: ContainerReader does not explicitly set a size; it automatically obtains the size in the following ways.

    • Fill the parent container: When ContainerReader is the only child component of the parent container, it automatically fills the parent container.
    • Fill the remaining space: When the parent container Flex, Row, or Column has child components that are not of the ContainerReader type, ContainerReader occupies the remaining space.
    • Allocate remaining space proportionally: When the parent container Flex, Row, or Column has multiple ContainerReader child components, ContainerReader allocates the remaining space via layoutWeight.

    Scenario 2: You can set size attributes such as layout constraints for ContainerReader to constrain its size. When a Flex, Row, or Column container has multiple ContainerReader child components, generally the first ContainerReader in your writing order will fill the remaining space, and the main axis size of the other ContainerReader components will be 0. When you set size constraints on a ContainerReader that appears earlier in the writing order, space is allocated to that ContainerReader according to the size constraints, and the remaining space is then allocated to the next ContainerReader.

    Taking the scenario of filling the parent container in Scenario 1 as an example, Flex allocates space equal to Flex's own size to the child component ContainerReader.

    import {ContainerReader,  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')
      }
    }

Implementing Independent Breakpoints

In the same component, you can use multiple ContainerReader components simultaneously, each with its own independent breakpoint state, enabling more refined layout control.

import {ContainerReader,  Size} from '@kit.ArkUI';
@Entry
@Component
struct MultiContainerExample {
  // The left and right ContainerReader components each have their own independent state variables and do not affect each other.
  @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 }) {
      // Left container, independently sensing its own size and breakpoint.
      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)

      // Right container, independently sensing its own size and breakpoint.
      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)
  }
}

Setting Column Count of a Grid Component Based on Container Breakpoints

In multi-device development, a grid component (such as Grid) can set different numbers of columns based on its own container size, implementing an adaptive list layout.

import {ContainerReader,  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 wraps Grid, dynamically adjusting the number of columns based on the container width breakpoint
      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)
            })
          }
          // Dynamically set column template based on breakpoint value
          .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)
  }

  // Return the corresponding column template based on the width breakpoint
  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';
    }
  }
}

Custom Component Adaptive Layout Based on Container Breakpoint

You can create custom components that use ContainerReader internally to implement adaptive internal layout, allowing the component to present the best effect in different usage scenarios.

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

// Adaptive card component, internally uses ContainerReader to sense the container size.
@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 })
  }
}

// Use containers of different sizes to demonstrate the adaptive effect of 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)
  }
}

Adaptive Left-Right Split Layout

On a master-detail page, the left side has a fixed-width Tab label, and the right side has an adaptive detail area. The right detail area uses ContainerReader to automatically switch the layout based on the remaining width: a vertical arrangement on narrow screens and a horizontal arrangement on wide screens.

import { ContainerReader,  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[] = ['Option 1', 'Option 2', 'Option 3', 'Option 4'];

  build() {
    Column() {
      Text('Split Layout')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      Flex({ direction: FlexDirection.Row }) {
        // Left: Vertical Tabs bar with a fixed width of 100.
        Tabs({ barPosition: BarPosition.Start, index: this.currentTabIndex }) {
          ForEach(this.menuItems, (item: string, index?: number) => {
            TabContent() {
              // Right: ContainerReader senses the TabContent area width
              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)
  }

  // Switch detail area layout based on breakpoint
  @Builder
  buildDetailContent() {
    if (this.widthBp === WidthBreakpoint.WIDTH_XS || this.widthBp === WidthBreakpoint.WIDTH_SM) {
      // Narrow screen: title and content arranged vertically
      Column({ space: 10 }) {
        Column() {
          Text('Title area')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
        }
        .width('100%')
        .height(60)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)

        Column() {
          Text('Content area')
            .fontSize(14)
        }
        .width('100%')
        .layoutWeight(1)
        .backgroundColor('#F0FAFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')
      .height('100%')
    } else {
      // Wide screen: title and content arranged horizontally
      Row({ space: 10 }) {
        Column() {
          Text('Title area')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
        }
        .width(120)
        .height('100%')
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)

        Column() {
          Text('Content area')
            .fontSize(14)
        }
        .layoutWeight(1)
        .height('100%')
        .backgroundColor('#F0FAFF')
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')
      .height('100%')
    }
  }
}

Vertical arrangement in narrow screen:

Horizontal arrangement in wide screen: