Read the original article:How to accurately count the exposure of items in a list
Problem Description
When a Scroll is nested in a List and the List scrolls along with the Scroll, how can I delay the counting of impressions after the scroll stops? In this case, the List's onScrollStop is not triggered, and if scrolling occurs again during the delayed reporting, it may lead to false reporting of items that are no longer visible.
Background Knowledge
Scroll Component features: The outer Scroll controls the overall scrolling behavior. The nested List does not trigger scroll events independently, and its scrolling state depends on the outer Scroll.
onScrollIndex: Triggered when a List subcomponent (such as ListItem) enters or exits the visible area, returning the first index (firstIndex), last index (lastIndex), and center index (centerIndex) of the current visible area.
onScrollStop: Triggered only once when the List scrolling stops completely. However, you need to ensure that the List does not disable edge effects (such as edgeEffect(EdgeEffect.None)), otherwise the callback may not work properly.
Solution
Continuously update the start and end indexes of the current visible area in the List's onScrollIndex, record the index snapshot at this time in onScrollStop, and set a 500ms timer for verification: If no new scrolling occurs within this time (that is, the current index does not change), it means that the visible area is stable and it is safe to report the exposure; otherwise, cancel the report to avoid misjudgment due to subsequent scrolling.
The steps are as follows:
1.Record the visible index in real time: Update startIndex and endIndex in the onScrollIndex of the List to ensure that these two state variables always reflect the first and last item indexes of the current visible area.
.onScrollIndex((firstIndex, lastIndex) => {
this.startIndex = firstIndex;
this.endIndex = lastIndex;
})
2.Start delayed verification after scrolling stops: In onScrollStop of the outer Scroll, save the current startIndex and endIndex as a snapshot (such as upStartIndex), and set a 500ms timer to verify whether a new scroll occurs.
.onScrollStop(() => {
let upStartIndex = this.startIndex;
let upEndIndex = this.endIndex;
setTimeout(() => {
if (upStartIndex !== this.startIndex) {
// Index changes, indicating that the scrolling continues and the report is canceled.
} else {
// Index is consistent, visible area is stable, and exposure reporting is performed
}
}, 500);
})
3.Determine whether it is a false alarm through index snapshot: Compare the snapshot index (upStartIndex) saved in the timer with the current index (this.startIndex). If they are consistent, it means that no new scrolling has occurred within 500ms, the visible area is stable, and the report can be submitted. If they are inconsistent, it means that subsequent scrolling has caused the item to become invisible, and the report needs to be canceled.
The complete code is as follows:
@Entry
@Component
struct NestedScroll {
@State listPosition: number = 0;
private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
private scrollerForScroll: Scroller = new Scroller();
private scrollerForList: Scroller = new Scroller();
@State startIndex: number = 0;
@State endIndex: number = 0;
build() {
Flex() {
Scroll(this.scrollerForScroll) {
List({ space: 20, scroller: this.scrollerForList }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('ListItem' + item)
.width('100%')
.height('100%')
.borderRadius(15)
.fontSize(16)
.textAlign(TextAlign.Center)
.backgroundColor(Color.White)
}
.width('100%')
.height(100)
}, (item: string) => item)
}
.width('100%')
.height('50%')
.edgeEffect(EdgeEffect.None)
.friction(0.6)
.onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
this.startIndex = firstIndex;
this.endIndex = lastIndex;
})
.onScrollStop(() => {
let upStartIndex = this.startIndex;
let upEndIndex = this.endIndex;
setTimeout(() => {
console.info('upStartIndex', upStartIndex);
console.info('this.startIndex', this.startIndex);
if (upStartIndex !== this.startIndex) {
this.getUIContext().showAlertDialog({
message: 'Output: Not reported',
autoCancel: true,
});
} else {
this.getUIContext().showAlertDialog({
message: 'Output: Report',
autoCancel: true,
});
}
}, 3000);
})
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor(0xDCDCDC)
.padding(20)
}
}
The effect diagram is as follows:

Top comments (0)