harmony 鸿蒙High-Performance WaterFlow Development
High-Performance WaterFlow Development
Background
The waterfall layout is a popular layout for presenting images and frequently seen in shopping and information applications. It is implemented using the WaterFlow component in ArkUI. This document discusses how to improve the WaterFlow performance, with practical examples.
Using Lazy Loading
Below shows the basic usage of the WaterFlow component.
build() {
Column({ space: 2 }) {
WaterFlow() {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
Column() {
Text("N" + item).fontSize(12).height('16')
Image('res/waterFlowTest (' + item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
// Set the <FlowItem> height to avoid the need to adapt to image heights.
.height(this.itemHeightArray[item])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
.columnsTemplate("1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('80%')
}
}
In the sample code, LazyForEach is used for lazy loading. During the layout of the WaterFlow component, the FlowItem components are created as needed based on the visible area; those that extend beyond the visible area are destroyed to reduce memory usage.
Considering that Image components are loaded asynchronously by default, you are advised to set the height for FlowItem components based on the image size, to avoid layout re-render caused by FlowItem components’ changing heights to accommodate images.
Implementing Infinite Scrolling
In the example, the fixed number of FlowItem components results in failure to achieve infinite scrolling.
To implement infinite scrolling with the capabilities provided by the WaterFlow component, you can new data to the LazyForEach data source during onReachEnd, and set the footer to the loading-new-data style (by using the <LoadingProgress> component).
build() {
Column({ space: 2 }) {
WaterFlow({ footer: this.itemFoot.bind(this) }) {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
Column() {
Text("N" + item).fontSize(12).height('16')
Image('res/waterFlowTest (' + item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height(this.itemHeightArray[item % 100])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
// Load data once the scrolling reaches the end of the page.
.onReachEnd(() => {
console.info("onReachEnd")
setTimeout(() => {
for (let i = 0; i < 100; i++) {
this.datasource.AddLastItem()
}
}, 1000)
})
.columnsTemplate("1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('80%')
}
}
// Add an item to the end of the data.
public AddLastItem(): void {
this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
this.notifyDataAdd(this.dataArray.length - 1)
}
To add new data, you must add an item to the end of the data. Do not directly modify the data array and use onDataReloaded() of LazyForEach to instruct the WaterFlow component to reload data.
Because the heights of the child components in WaterFlow vary, and the position of a lower child component depends on its upper one, reloading all data in WaterFlow will trigger waterfall layout recalculation, causing frame freezing. In comparison, if you add new data by adding an item to the end of the data and then call notifyDataAdd(this.dataArray.length - 1), the WaterFlow component loads new data, without processing existing data repeatedly.
Adding Data in Advance
Although infinite scrolling can be achieved through triggering of onReachEnd() upon new data, there may be an obvious pause in the process of loading new data when the user scrolls to the bottom.
To create a smooth scrolling experience, you need to adjust the time for adding new data. For example, you can add some new data in advance when the LazyForEach data source still has several pieces of data left before iteration ends.
build() {
Column({ space: 2 }) {
WaterFlow() {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
Column() {
Text("N" + item).fontSize(12).height('16')
Image('res/waterFlowTest (' + item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
.onAppear(() => {
// Add data in advance when scrolling is about to end.
if (item + 20 == this.datasource.totalCount()) {
for (let i = 0; i < 100; i++) {
this.datasource.AddLastItem()
}
}
})
.width('100%')
.height(this.itemHeightArray[item % 100])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
.columnsTemplate("1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('80%')
}
}
In this example, the quantity of data items left till the end is determined in onAppear of FlowItem, and new data is added in advance to implement stutter-free infinite scrolling.
Reusing Components
Now that we have a waterfall that scrolls infinitely without explicitly waiting for more data, we can further optimize its performance by making the components reusable.
During scrolling, FlowItem and its child component are frequently created and destroyed. By encapsulating the component in FlowItem into a custom component and decorating it with the @Reusable decorator, you make the component reusable, reducing the overhead of repeatedly creating and destroying nodes in the ArkUI framework. For details about component reuse, see Best Practices for Component Reuse.
build() {
Column({ space: 2 }) {
WaterFlow() {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
// Use reusable custom components.
ResuableFlowItem({ item: item })
}
.onAppear(() => {
// Add data in advance when scrolling is about to end.
if (item + 20 == this.datasource.totalCount()) {
for (let i = 0; i < 100; i++) {
this.datasource.AddLastItem()
}
}
})
.width('100%')
.height(this.itemHeightArray[item % 100])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
.columnsTemplate("1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('80%')
}
}
@Reusable
@Component
struct ResuableFlowItem {
@State item: number = 0
// Invoked when a reusable custom component is re-added to the component tree from the reuse cache. The component state variable can be updated here to display the correct content.
aboutToReuse(params) {
this.item = params.item;
}
build() {
Column() {
Text("N" + this.item).fontSize(12).height('16')
Image('res/waterFlowTest (' + this.item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
}
Takeaway
To achieve the optimal performance in infinite scrolling, you can use WaterFlow with the LazyForEach rendering control syntax, ahead-of-time data addition, and component reuse.
你可能感兴趣的鸿蒙文章
harmony 鸿蒙Application Animation Practice
harmony 鸿蒙More Performance Improvement Methods
harmony 鸿蒙Best Practices for Component Reuse
harmony 鸿蒙Secure and Efficient N-API Development
harmony 鸿蒙Efficient Concurrent Programming
harmony 鸿蒙Flex Layout Performance Improvement
- 所属分类: 后端技术
- 本文标签:
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦