Published 2 months ago

Mastering Adaptive Layouts in HarmonyOS Next

Software Development
Mastering Adaptive Layouts in HarmonyOS Next

Mastering Adaptive Layouts in HarmonyOS Next

HarmonyOS Next's adaptive layout system empowers developers to create dynamic and visually appealing interfaces across diverse devices. This article delves into the core principles and seven key capabilities of this powerful system, illustrated with practical code examples and best practices.

Basic Principles of Adaptive Layouts

At its heart, HarmonyOS Next's adaptive layout relies on defining relative relationships between UI components. Instead of fixed sizes, components adjust their positions and dimensions in response to changes in the parent container's size. Think of it as arranging furniture in a room that can expand or contract; the furniture dynamically rearranges to maintain a pleasing and functional layout. This adaptability is achieved through layout attributes, allowing components to intelligently react to container resizing.

Components adapt in various ways: stretching, shrinking, hiding, or rearranging depending on predefined rules. Let's explore the specific capabilities in detail.

Detailed Explanation of the Seven Adaptive Layout Capabilities

1. Stretching Capability

The stretching capability allocates any extra space in the container proportionally to components with a non-zero flexGrow value. Conversely, flexShrink determines how components reduce their size if the available space is less than the total component size. flexBasis defines the initial size of a flex item before any stretching or shrinking occurs. These attributes are commonly used together.

@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)
    }
}

In this example, the central image stretches when the container expands, while the side rows shrink when the container contracts.

2. Equal Division Capability

This distributes available space equally among child components. It's achieved using justifyContent: FlexAlign.SpaceEvenly on Row, Column, or Flex components.

@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)
    }
}

Ideal for evenly spacing menu items or similar UI elements.

3. Proportion Capability

Sub-components resize proportionally to the container using percentages or layoutWeight. Note: layoutWeight only functions within Row, Column, or Flex containers, and intrinsic component sizes are overridden.

@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)
    }
}

4. Scaling Capability

Maintain aspect ratios while scaling. Use percentage-based layouts combined with aspectRatio.

@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)
    }
}

5. Extension Capability

Display or hide sub-components sequentially as the container size changes. Use List or Scroll components with Row/Column.

@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)
    }
}

6. Hiding Capability

Control the visibility of components based on display priority (displayPriority). Components with lower priority are hidden first when space is constrained.

@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)
    }
}

7. Wrapping Capability

Content wraps to the next line when the available space in the layout direction is insufficient. Use FlexWrap.Wrap with the Flex component.

@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('#F1F3F5')
    }

}

Conclusion

HarmonyOS Next's adaptive layout provides a robust and flexible framework for creating responsive user interfaces. Mastering these seven capabilities enables developers to build elegant and efficient applications that seamlessly adapt to various screen sizes and orientations. Further exploration of the HarmonyOS documentation will enhance your proficiency in these techniques.

Hashtags: #HarmonyOS # AdaptiveLayout # UI # UX # ResponsiveDesign # HarmonyOSNext # Flexbox # Layout # MobileDevelopment # AppDevelopment

Related Articles

thumb_nail_Unveiling the Haiku License: A Fair Code Revolution

Software Development

Unveiling the Haiku License: A Fair Code Revolution

Dive into the innovative Haiku License, a game-changer in open-source licensing that balances open access with fair compensation for developers. Learn about its features, challenges, and potential to reshape the software development landscape. Explore now!

Read More
thumb_nail_Leetcode - 1. Two Sum

Software Development

Leetcode - 1. Two Sum

Master LeetCode's Two Sum problem! Learn two efficient JavaScript solutions: the optimal hash map approach and a practical two-pointer technique. Improve your coding skills today!

Read More
thumb_nail_The Future of Digital Credentials in 2025: Trends, Challenges, and Opportunities

Business, Software Development

The Future of Digital Credentials in 2025: Trends, Challenges, and Opportunities

Digital credentials are transforming industries in 2025! Learn about blockchain's role, industry adoption trends, privacy enhancements, and the challenges and opportunities shaping this exciting field. Discover how AI and emerging technologies are revolutionizing identity verification and workforce management. Explore the future of digital credentials today!

Read More
Your Job, Your Community
logo
© All rights reserved 2024