A Comprehensive Analysis of the Adaptive Layout Capabilities in HarmonyOS Next
This article aims to deeply explore the technical details of the Huawei HarmonyOS Next system and summarize them based on practical development practices. It mainly serves as a medium for technical sharing and communication. There may inevitably be some errors or omissions. Colleagues are welcome to put forward valuable opinions and questions so that we can make progress together. This article is original content, and any form of reprint must indicate the source and the original author.
In the development world of HarmonyOS Next, the adaptive layout is a powerful assistant for building flexible and aesthetically pleasing interfaces. Today, let's conduct an in-depth analysis of its principles, capabilities, and applications in actual projects.
Basic Principles of the Adaptive Layout
The core of the adaptive layout lies in establishing the relative relationships between components, enabling elements to dynamically adapt according to changes in the container. It's like arranging furniture in a stretchable room, where the distances and sizes between the furniture will automatically adjust as the room's size changes. In HarmonyOS Next, this relative relationship is achieved through various layout attributes, allowing components to intelligently respond to changes in the container's size.
There are various ways for elements to dynamically adapt. For example, when the container's size changes, sub-components can be stretched, shrunk, hidden, or rearranged according to set rules to ensure a reasonable layout effect in containers of different sizes. This process involves the comprehensive application of various layout capabilities, and let's take a detailed look at them below.
A Detailed Explanation of the Seven Capabilities of the Adaptive Layout
-
Stretching Capability: The stretching capability allows all the space added or reduced when the size of the container component changes to be allocated to the specified area. It is achieved through the
flexGrow
andflexShrink
attributes of the Flex layout, and these two attributes are often used in conjunction with theflexBasis
attribute. For example, in a layout that includes an image and a blank area:
@Entry
@Component
struct StretchSample {
@State containerWidth: number = 402
@Builder slider() {
Slider({ value: this.containerWidth, min: 402, max: 1000, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.containerWidth = value;
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row() {
Row().width(150).height(400).backgroundColor('#FFFFFF').flexGrow(0).flexShrink(1)
Image($r("app.media.illustrator")).width(400).height(400)
.objectFit(ImageFit.Contain)
.backgroundColor("#66F1CCB8")
.flexGrow(1).flexShrink(0)
Row().width(150).height(400).backgroundColor('#FFFFFF').flexGrow(0).flexShrink(1)
}
.width(this.containerWidth)
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
When the size of the parent container is larger than the base size, the middle image area will stretch; when it is smaller than the base size, the blank areas on both sides will shrink.
-
Equal Distribution Capability: The equal distribution capability is used to evenly distribute the space when the size of the container component changes to all the blank areas. It is achieved by setting the
justifyContent
attribute of theRow
,Column
, orFlex
component toFlexAlign.SpaceEvenly
. For example, in the bottom menu bar:
@Entry
@Component
struct EqualDistributionSample {
readonly list: number[] = [0, 1, 2, 3]
@State rate: number = 0.6
@Builder slider() {
Slider({ value: this.rate * 100, min: 30, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row() {
ForEach(this.list, (item: number) => {
Column() {
Image($r("app.media.startIcon")).width(48).height(48).margin({ top: 8 })
Text('Menu Option')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}
.width(80)
.height(102)
.flexShrink(1)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
Each menu option will be evenly distributed in the parent container.
-
Proportion Capability: The width or height of the sub-component changes with the container component according to a preset proportion. It can be achieved by setting the width and height of the sub-component as a percentage of the width and height of the parent component, or by using the
layoutWeight
attribute. However, it should be noted that thelayoutWeight
attribute only takes effect when the parent container isRow
,Column
, orFlex
, and the size of the component itself will become invalid after setting. For example:
@Entry
@Component
struct ProportionSample {
@State rate: number = 0.5
@Builder slider() {
Slider({ value: 100, min: 25, max: 50, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row() {
Column() {
Image($r("app.media.down")).width(48).height(48)
}
.height(96)
.layoutWeight(1)
Column() {
Image($r("app.media.pause")).width(48).height(48)
}
.height(96)
.layoutWeight(1)
Column() {
Image($r("app.media.next")).width(48).height(48)
}
.height(96)
.layoutWeight(1)
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
The "Previous Song", "Play/Pause", and "Next Song" buttons evenly divide the space of the parent container in a ratio of 1:1:1.
-
Scaling Capability: The scaling capability enables the width and height of the sub-component to change with the container component according to a preset proportion, and the aspect ratio remains unchanged. It is achieved through percentage layout in combination with the
aspectRatio
attribute. For example:
@Entry
@Component
struct ScaleSample {
@State sliderWidth: number = 400
@State sliderHeight: number = 400
@Builder slider() {
Slider({ value: this.sliderHeight, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.sliderHeight = value
})
.position({ x: '20%', y: '80%' })
Slider({ value: this.sliderWidth, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.sliderWidth = value;
})
.position({ x: '20%', y: '87%' })
}
build() {
Column() {
Column() {
Column() {
Image($r("app.media.illustrator")).width('100%').height('100%')
}
.aspectRatio(1)
.border({ width: 2, color: "#66F1CCB8"})
}
.backgroundColor("#FFFFFF")
.height(this.sliderHeight)
.width(this.sliderWidth)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor("#F1F3F5")
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
The Column component where the image is located will scale with the parent container and always maintain an aspect ratio of 1.
-
Extension Capability: The sub-components within the container component are displayed or hidden in order as the container size changes. It can be achieved by using the
List
component or theScroll
component in combination with theRow
/Column
component. For example:
@Entry
@Component
struct ExtensionSample {
@State rate: number = 0.60
readonly appList: number[] = [0, 1, 2, 3, 4, 5, 6, 7]
@Builder slider() {
Slider({ value: this.rate * 100, min: 8, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row({ space: 10 }) {
List({ space: 10 }) {
ForEach(this.appList, (item: number) => {
ListItem() {
Column() {
Image($r("app.media.startIcon")).width(48).height(48).margin({ top: 8 })
Text('App Icon')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}
.width(80).height(102)
}
})
}
.padding({ top: 16, left: 10 })
.listDirection(Axis.Horizontal)
.width('100%')
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
As the size of the parent container changes, the number of displayed icons will change accordingly.
-
Hiding Capability: The sub-components are displayed or hidden according to the preset display priority as the container size changes. It is achieved by setting the
displayPriority
attribute and is often used in scenarios where the displayed content varies at different resolutions. For example:
@Entry
@Component
struct HiddenSample {
@State rate: number = 0.45
@Builder slider() {
Slider({ value: this.rate * 100, min: 10, max: 45, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row({ space:24 }) {
Image($r("app.media.favorite")).width(48).height(48).objectFit(ImageFit.Contain).displayPriority(1)
Image($r("app.media.down")).width(48).height(48).objectFit(ImageFit.Contain).displayPriority(2)
Image($r("app.media.pause")).width(48).height(48).objectFit(ImageFit.Contain).displayPriority(3)
Image($r("app.media.next")).width(48).height(48).objectFit(ImageFit.Contain).displayPriority(2)
Image($r("app.media.list")).width(48).height(48).objectFit(ImageFit.Contain).displayPriority(1)
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
When the container space is insufficient, elements are hidden according to the priority.
-
Wrapping Capability: When the size of the container component changes and the size in the layout direction is not sufficient to display the complete content, it will automatically wrap to the next line. It is achieved by setting the
wrap
property of theFlex
component toFlexWrap.Wrap
. For example:
arkts
@Entry
@Component
struct WrapSample {
@State rate: number = 0.7
readonly imageList: Resource[] = [
$r('app.media.flexWrap1'),
$r('app.media.flexWrap2'),
$r('app.media.flexWrap3'),
$r('app.media.flexWrap4'),
$r('app.media.flexWrap5'),
$r('app.media.flexWrap6')
]
@Builder slider() {
Slider({ value: this.rate * 100, min: 50, max: 70, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '87%' })
}
build() {
Flex({ justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) {
Column() {
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Center,
wrap: FlexWrap.Wrap
}) {
ForEach(this.imageList, (item:Resource) => {
Image(item).width(183).height(138).padding(10)
})
}
.backgroundColor('#FFFFFF')
.padding(20)
.width(this.rate * 100 + '%')
.borderRadius(16)
}
.width('100%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F
Top comments (0)