Read the original article:How to get the position of the first visible and last visible ListItem
How to get the position of the first visible and last visible ListItem
Requirement Description
When scrolling a horizontal List component, how to dynamically track the indices of the first fully visible and last fully visible ListItem
Background Knowledge
- List: A container for displaying a series of items with uniform width, suitable for presenting homogeneous data (e.g., images and text).
-
ListItem: Must be used within
Listto render individual items. - Events:
-
onAreaChange: Triggered when component dimensions change, useful for obtaining coordinates and size. -
onScrollIndex: Fired when the first/last visible child index changes during scrolling. -
onScrollStop: Triggered when scrolling stops.
-
-
Limitation:
onScrollIndextreatsListItemGroupas a single index (ignores innerListItemindices). UseonVisibleAreaChangefor grouped lists.
Implementation Steps
- Use
onAreaChangeto getListwidth for visibility checks. - Initialize indices via
onScrollIndex, then update them inonScrollStop. - Adjust indices for partial visibility:
- If the first item is partially hidden, use the next item.
- If the last item is partially hidden, use the previous item.
Code Snippet / Configuration
@Entry
@Component
struct ListExample {
private arr: number[] = []
private scrollerForList: Scroller = new Scroller()
@State startStr: string = ''
startIndex = 0 // First visible item
endIndex = 0 // Last visible item
initState = true
listWidth: number = 0 // List width
// Adjust indices for partial visibility (±3vp tolerance)
private judgeVisible() {
this.startStr = ""
let rect = this.scrollerForList.getItemRect(this.startIndex)
if (rect.x < -3) this.startIndex++ // Left-clipped item
rect = this.scrollerForList.getItemRect(this.endIndex)
if (rect.x + rect.width > this.listWidth + 3) this.endIndex-- // Right-clipped item
this.startStr = (this.startIndex <= this.endIndex)
? `${this.startIndex},${this.endIndex}`
: "No fully visible items"
}
aboutToAppear() {
for (let i = 0; i < 20; i++) this.arr.push(i)
}
build() {
Column({ space: 20 }) {
Text(this.startStr).fontColor(Color.Red).height(100).width("100%")
Row() {
List({ space: 20, initialIndex: 0, scroller: this.scrollerForList }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('' + item).width('100%').height(100)
}
.width(item % 4 == 0 ? 500 : 50).height(200)
}, (item: number) => item.toString())
}
.listDirection(Axis.Horizontal)
.onAreaChange((_, newValue) => { this.listWidth = newValue.width as number })
.onScrollIndex((start, end) => {
this.startIndex = start
this.endIndex = end
if (this.initState) { this.judgeVisible(); this.initState = false }
})
.onScrollStop(() => {
const [tmpStart, tmpEnd] = [this.startIndex, this.endIndex]
this.judgeVisible()
this.startIndex = tmpStart
this.endIndex = tmpEnd
})
}.width('100%').height('100%')
}
}
}
Handling ListItemGroup
ListItemGroup({ header: this.ListGroupHead(itemGroup.title) }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(item.toString())
}
.onVisibleAreaChange([0, 1], (isVisible: boolean, currRatio: number) => {
// Logic for visibility changes (e.g., show header button for topmost group)
})
})
}
Test Results
Correctly identifies fully visible items in horizontal List.
Limitations
onScrollIndex ignores nested ListItem indices within ListItemGroup.
Requires manual adjustment for partial visibility thresholds (e.g., 3vp tolerance).
Related Documents
https://developer.huawei.com/consumer/en/doc/harmonyos-references/ts-container-list
Top comments (0)