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
- Wrap all tags/items in a Flex container.
- Set
flexDirectiontoRowand enablewrapso items can wrap to multiple lines. - Use the
aboutToAppearlifecycle 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. - Use
constraintSizeto set the maximum height of the Flex container corresponding to the maximum number of rows. - Display a button if the content exceeds the maximum number of rows.
- Clicking the button toggles the Flex container’s height between
maxHeightand full height.
Scenario 2: Button located inside the Flex layout
- Use
display.getDefaultDisplaySyncto get the screen width. - Use
measureTextto calculate the width of each text item. - Calculate the number of items per row and render items along with the expand/collapse button inside the Flex container.
- 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)
}
}
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 })
}
}
| 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


Top comments (0)