DEV Community

HarmonyOS
HarmonyOS

Posted on

Using Flex layout to expand and collapse subitems

Read the original article:Using Flex layout to expand and collapse subitems

Using Flex layout to expand and collapse subitems

Problem Description

When using Flexbox layout, the Flexbox height will default to the maximum height. The desired scenario is: when a child item exceeds the maximum height, the Flexbox will automatically expand to the maximum height; when a child item's height is less than the maximum height, the Flexbox will maintain the child item's height. Flexbox can display a fixed number of rows; when the content exceeds this fixed number of rows, it can be expanded or collapsed by clicking buttons.

Background Knowledge

  • Flex Layout: A container component that arranges child components flexibly, providing efficient ways to align, distribute, and manage remaining space among child elements.
  • aboutToAppear lifecycle function: Executes before the build() function when creating a new instance of a custom component.
  • constraintSize method: Sets the maximum size constraints for a component during layout.
  • measureText: Calculates the width of a specified text string.
  • display.getDefaultDisplaySync: Obtains the default display object, which provides screen attributes and methods for layout calculations.

Solution

Scenario 1: Button located outside the Flex layout

  1. Wrap all tags/items in a Flex container.
  2. Set flexDirection to Row and enable wrap so items can wrap to multiple lines.
  3. Use the aboutToAppear lifecycle method to dynamically calculate each item's width and height. Determine the number of items per row based on the container width and maximum child width.
  4. Use constraintSize to set the maximum height of the Flex container corresponding to the maximum number of rows.
  5. Display a button if the content exceeds the maximum number of rows.
  6. Clicking the button toggles the Flex container’s height between maxHeight and full height.

Scenario 2: Button located inside the Flex layout

  1. Use display.getDefaultDisplaySync to get the screen width.
  2. Use measureText to calculate the width of each text item.
  3. Calculate the number of items per row and render items along with the expand/collapse button inside the Flex container.
  4. Dynamically adjust the displayed items and Flex height when the button is clicked.

Scenario 1: Button outside Flex

const childMaxWidth: number = 325

@Component
struct TextItem {
  @State message: string = ''
  @Prop fontSizeData: number

  build() {
    Text(this.message)
      .fontSize(this.fontSizeData)
      .margin({ left: 10, top: 10 })
      .backgroundColor('#ff6e56cb')
      .constraintSize({ maxWidth: childMaxWidth + 'px' })
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .padding({ left: 5, right: 5 })
      .height(40)
  }
}

@Entry
@Component
struct Index2 {
  @State FlexWidth: string = '80%';
  @State heightControl: number | string = 0;
  @State maxHeight: number = 0;
  @State maxNumber: number = 2;
  @State ButtonText: string = '';
  @State AllData: string[] = ['1111', '2211', '3331', '44444', '51115', '666', '7777']
  @State fontSizeData: number = 14
  @State isShowButton: boolean = false

  aboutToAppear(): void {
    this.maxHeight = this.maxNumber * 50
    this.heightControl = this.maxHeight
    if (this.AllData.length > 0) {
      if (this.maxNumber > 0) {
        let count = this.AllData.length
        let lineCount = count / 3
        if (lineCount > this.maxNumber) {
          this.isShowButton = true
        }
      }
    }
  }

  build() {
    Row() {
      Column() {
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(
            this.AllData,
            (item: string) => {
              TextItem({ message: item, fontSizeData: this.fontSizeData })
            }
          )
        }
        .constraintSize({ maxHeight: this.heightControl })
        .border({ width: 1 })
        .width(this.FlexWidth)
        .padding({ bottom: 10 })
        .clip(true)

        Button(this.ButtonText)
          .onClick(() => {
            if (this.heightControl === this.maxHeight) {
              this.heightControl = '100%'
              this.ButtonText = '^'
            } else {
              this.heightControl = this.maxHeight
              this.ButtonText = 'v'
            }
          })
          .width(40)
          .height(30)
          .margin({ left: 10, top: 10 })
          .visibility(this.isShowButton ? Visibility.Visible : Visibility.Hidden)
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor(Color.White)
  }
}
Enter fullscreen mode Exit fullscreen mode

cke_10348.png

Scenario 2: Button inside Flex

import { display, MeasureUtils } from '@kit.ArkUI'

let displayClass: display.Display | null = null
let componentWidth: number = 0
const childMaxWidth: number = 500
try {
  displayClass = display.getDefaultDisplaySync()
  componentWidth = displayClass.width
} catch (exception) {
  console.error(`Failed to obtain the default display object: ${exception}`);
}

@Entry
@Component
struct FlexOverNumber {
  @State @Watch('IndexChange') index: number = 0
  @State @Watch('textChange') message: string = ''
  @State flexWidth: string = '80%'
  @State newIndex: number = 0
  @State heightControl: number | string = 100
  @State buttonText: string = ''
  @State allData: string[] =
    ['1', '22', '333', '44444', '55', '66666', '8888888888888', '99', '7777', '1111', '6666', '88888', '99999']
  @State someData: string[] = []
  @State showData: string[] = []
  @State fontSizeData: number = 14
  @State allWidth: number = 0
  @State textWidth: number = 0
  @State restrictWidth: number = 0

  IndexChange() {
    if (this.allWidth >= (this.restrictWidth - childMaxWidth) && this.allWidth <= this.restrictWidth) {
      this.newIndex = this.index
    }
  }

  textChange() {
    let content: string = this.message
    this.textWidth =
      this.getUIContext().getMeasureUtils().measureText({ textContent: content, fontSize: this.fontSizeData })
    this.allWidth += Math.min(this.textWidth, childMaxWidth)
  }

  aboutToAppear(): void {
    if (componentWidth !== 0) {
      this.restrictWidth = componentWidth * 1.2
    }
    for (let i = 0; i < this.allData.length; i++) {
      this.message = this.allData[i]
      this.index = i
    }
    this.someData = this.allData.slice(0, this.newIndex + 1)
    this.showData = this.someData
  }

  build() {
    Row() {
      Column() {
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.showData, (item: string) => {
            TextItem({ message: item, fontSizeData: this.fontSizeData })
          })
          Button(this.buttonText).onClick(() => {
            if (this.heightControl === 100) {
              this.heightControl = '100%'
              this.buttonText = '^'
              this.showData = this.allData
            } else {
              this.heightControl = 100
              this.buttonText = 'v'
              this.showData = this.someData
            }
          }).width(40).height(30).margin({left: 8})
        }
        .constraintSize({ maxHeight: this.heightControl })
        .border({ width: 1 })
        .width(this.flexWidth)
        .margin({ left: '5%' })
        .clip(true)
      }.width('100%')
    }.height('100%')
    .backgroundColor(Color.White)
  }
}

@Component
struct TextItem {
  @State message: string = ''
  @Prop fontSizeData: number

  build() {
    Text(this.message)
      .fontSize(this.fontSizeData)
      .margin({ left: 10, top: 10 })
      .backgroundColor('#ff9382d6')
      .constraintSize({ maxWidth: childMaxWidth + 'px' })
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
  }
}
Enter fullscreen mode Exit fullscreen mode

cke_11546.png

Solution Applicable Scenarios illustrate
Tag Flow Layout The button is located outside the Flex layout. It can greatly reduce the difficulty and amount of code, making it easier to maintain.
Calculate the number of sub-items displayed in each row directly. The button is located within a Flex layout. When there are too many child items in a Flex container, calculating the button's location directly can improve code execution efficiency.

Verification Result

The Flex layout correctly expands and collapses based on content height, displaying a fixed number of rows by default and dynamically adjusting its size and visible items when the expand/collapse button is triggered in both layout scenarios.

Related Documents or Links

https://developer.huawei.com/consumer/en/doc/harmonyos-references/js-apis-display

https://developer.huawei.com/consumer/en/doc/harmonyos-references/ts-container-flex

Written by Emine Inan

Top comments (0)