Read the original article:How to Implement an Automatically Scrolling List Using the List Component
How to Implement an Automatically Scrolling List Using the List Component
Problem Description
How to implement a list that can both auto-scroll and loop infinitely using the List component? The list needs to have an auto-scrolling feature and be able to achieve a seamless looping effect, ensuring that the first and last items connect naturally without any gaps or stuttering.
Background Knowledge
The List component is a scrollable container component that contains a series of list items of the same width. It is suitable for presenting similar data in multiple rows, such as images and text.
The setInterval timer can call a function repeatedly, with a fixed time delay between each call.
The scrollTo function of ListScroller can control the scrolling of the List.
Solution
Scene 1: Implementing a vertically scrolling list:
1.The automatic scrolling effect is achieved by using the setInterval timer to repeatedly execute this.scroller.scrollTo. The specific logic is as follows: when the movement exceeds five lines, the data at index 0 is removed from the data array each time, and this data is then added to the end of the array, thereby implementing a loop.
startAutoRoll() {
this.last = new Date().getTime();
this.IntervalNum = setInterval(() => {
if (this.rollOffset > (this.itemHeight * 5)) {
for (let i = 0; i < 5; i++) {
// Subtract from the front and add to the back of the data array to achieve a circular effect.
this.data.deleteData(0)
this.data.pushData(this.nextNum.toString())
if (this.nextNum === 9) {
this.nextNum = 0
} else {
this.nextNum++
}
}
// Corresponding to data changes, prevent exceeding limits
this.rollOffset -= this.itemHeight * 5
}
let curr = new Date().getTime();
this.rollOffset += 0.5 * (curr - this.last) / 10
this.scroller.scrollTo({ xOffset: 0, yOffset: this.rollOffset, animation: false })
this.last = curr;
}, 10)
}
2.In the onScrollFrameBegin callback of the List, calculate the actual required scroll amount and return it as the return value of the event handler function. The List will scroll according to the actual scroll amount returned.
.onScrollFrameBegin((offset: number, state: ScrollState) => {
let currOffset = this.scroller.currentOffset().yOffset;
let newOffset = currOffset + offset;
let totalHeight = this.itemHeight * 10;
// Swipe up
if (newOffset < totalHeight * 0.5) {
newOffset += totalHeight;
// Decline
} else if (newOffset > totalHeight * 1.5) {
newOffset -= totalHeight
}
this.rollOffset = newOffset
return { offsetRemain: newOffset - currOffset }
})
The complete code example is as follows:
Index.ets:
import { MyDataSource } from './MyDataSource';
@Entry
@Component
struct Parent {
private dataSource: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
private nextNum: number = 0
private data: MyDataSource = new MyDataSource();
private scroller: Scroller = new Scroller();
private rollOffset: number = 0
private intervalNum: number = 0
pathStack: NavPathStack = new NavPathStack()
private itemHeight: number = 50;
private last: number = 0;
startAutoRoll() {
this.last = new Date().getTime();
this.intervalNum = setInterval(() => {
if (this.rollOffset > (this.itemHeight * 5)) {
for (let i = 0; i < 5; i++) {
// Subtract from the front and add to the back of the data array to achieve a circular effect.
this.data.deleteData(0)
this.data.pushData(this.nextNum.toString())
if (this.nextNum === 9) {
this.nextNum = 0
} else {
this.nextNum++
}
}
// Corresponding to data changes, prevent exceeding limits
this.rollOffset -= this.itemHeight * 5
}
let curr = new Date().getTime();
this.rollOffset += 0.5 * (curr - this.last) / 10
this.scroller.scrollTo({ xOffset: 0, yOffset: this.rollOffset, animation: false })
this.last = curr;
}, 10)
}
// Two floors
aboutToAppear(): void {
for (let i = 0; i < 10; i++) {
this.data.pushData(this.dataSource[i].toString())
}
for (let i = 0; i < 10; i++) {
this.data.pushData(this.dataSource[i].toString())
}
this.startAutoRoll()
}
build() {
Navigation(this.pathStack) {
Row() {
List({ scroller: this.scroller }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Column() {
Text('How to help your baby fall asleep quickly after feeding.' + item.toString())
.fontSize(12)
.textAlign(TextAlign.Start)
.margin({ left: 8 })
Row() {
Text('Get a response in 20 seconds')
.fontSize(10)
.textAlign(TextAlign.Start)
.fontColor('#ffa933ba')
.margin({ left: 8, bottom: 8 })
Row() {
Text('One mother is answering.')
.fontSize(8)
.fontColor('#ff656266')
.margin({ right: 16, bottom: 12 })
Image($r('app.media.startIcon'))
.width(15)
.height(15)
.margin({ right: 16, bottom: 12 })
}
}
.margin({ bottom: 10, top: 5 })
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({ left: 10, top: 5 })
.alignItems(HorizontalAlign.Start)
.width('100%')
}
.onClick(() => {
console.info(`asdddddd=${item}`)
})
.borderRadius(10)
.backgroundColor('#FFFFFF')
.height(40)
.width('90%')
.margin({
left: '4.5%',
right: '2%',
top: this.itemHeight * 0.1,
bottom: this.itemHeight * 0.1
})
}, (item: string) => item)
}
.scrollBar(BarState.Off)
.width('100%')
.height(160)
.backgroundColor('#FFDCDCDC')
.listDirection(Axis.Vertical)
.scrollSnapAlign(ScrollSnapAlign.NONE)
.friction(0.5)
.onScrollStart(() => {
clearInterval(this.intervalNum)
})
.onScrollStop(() => {
this.startAutoRoll()
})
.onScrollFrameBegin((offset: number, state: ScrollState) => {
let currOffset = this.scroller.currentOffset().yOffset;
let newOffset = currOffset + offset;
let totalHeight = this.itemHeight * 10;
// Swipe up
if (newOffset < totalHeight * 0.5) {
newOffset += totalHeight;
// Decline
} else if (newOffset > totalHeight * 1.5) {
newOffset -= totalHeight
}
this.rollOffset = newOffset
return { offsetRemain: newOffset - currOffset }
})
}
}
}
}
BasicDataSource.ets:
export class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private originDataArray: string[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): string {
return this.originDataArray[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
MyDataSource.ets:
import { BasicDataSource } from './BasicDataSource';
export class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index % this.dataArray.length];
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public moveDataWithoutNotify(from: number, to: number): void {
let tmp = this.dataArray.splice(from, 1);
this.dataArray.splice(to, 0, tmp[0])
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
public deleteData(index: number): void {
this.dataArray.splice(index, 1);
this.notifyDataDelete(index);
}
}
The vertical loop scrolling effect is as follows:
Scene Two: Implementing a horizontally scrolling list. Horizontal scrolling is similar to vertical scrolling. The complete code is as follows (BasicDataSource.ets and MyDataSource.ets are the same as in Scene One, and only the Index.ets code is provided below).
import { MyDataSource } from './MyDataSource';
@Entry
@Component
struct Index {
private dataSource: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
private nextNum: number = 0
private data: MyDataSource = new MyDataSource();
private scroller: Scroller = new Scroller();
private rollOffset: number = 0
private intervalNum: number = 0
pathStack: NavPathStack = new NavPathStack()
private itemWidth: number = 350; // Change itemHeight=50 in the vertical direction to itemWidth=350.
private last: number = 0;
startAutoRoll() {
this.last = new Date().getTime();
this.intervalNum = setInterval(() => {
if (this.rollOffset > (this.itemWidth * 5)) {
for (let i = 0; i < 5; i++) {
// Subtract from the front and add to the back of the data array to achieve a circular effect.
this.data.deleteData(0)
this.data.pushData(this.nextNum.toString())
if (this.nextNum === 9) {
this.nextNum = 0
} else {
this.nextNum++
}
}
// Corresponding to data changes, prevent exceeding limits
this.rollOffset -= this.itemWidth * 5
}
let curr = new Date().getTime();
this.rollOffset += 0.5 * (curr - this.last) / 10
//Change to x-axis movement
this.scroller.scrollTo({ xOffset: this.rollOffset, yOffset: 0, animation: false })
this.last = curr;
}, 10)
}
// Two floors
aboutToAppear(): void {
for (let i = 0; i < 10; i++) {
this.data.pushData(this.dataSource[i].toString())
}
for (let i = 0; i < 10; i++) {
this.data.pushData(this.dataSource[i].toString())
}
this.startAutoRoll()
}
build() {
Navigation(this.pathStack) {
Row() {
List({ scroller: this.scroller }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Column() {
Text('How to help your baby fall asleep quickly after feeding.' + item.toString())
.fontSize(12)
.textAlign(TextAlign.Start)
.margin({ left: 8 })
Row() {
Text('Get a response in 20 seconds')
.fontSize(10)
.textAlign(TextAlign.Start)
.fontColor('#ffa933ba')
.margin({ left: 8, bottom: 8 })
Row() {
Text('One mother is answering.')
.fontSize(8)
.fontColor('#ff656266')
.margin({ right: 16, bottom: 12 })
Image($r('app.media.startIcon'))
.width(15)
.height(15)
.margin({ right: 16, bottom: 12 })
}
}
.margin({ bottom: 10, top: 5 })
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({ left: 10, top: 5 })
.alignItems(HorizontalAlign.Start)
.width('100%')
}
.onClick(() => {
console.info(`asdddddd= ${item}`)
})
.borderRadius(10)
.backgroundColor('#FFFFFF')
.height(40)
.width('90%')
.margin({
left: '2%',
right: '2%',
top: 50 * 0.1,
bottom: 50 * 0.1
})
}, (item: string) => item)
}
.scrollBar(BarState.Off)
.width('100%')
.height(160)
.backgroundColor('#FFDCDCDC')
.listDirection(Axis.Horizontal) // Change the sliding direction to horizontal.
.scrollSnapAlign(ScrollSnapAlign.NONE)
.friction(0.5)
.onScrollStart(() => {
clearInterval(this.intervalNum)
})
.onScrollStop(() => {
this.startAutoRoll()
})
.onScrollFrameBegin((offset: number, state: ScrollState) => {
let currOffset = this.scroller.currentOffset().xOffset; // Change to the x-axis
let newOffset = currOffset + offset;
let totalWidth = this.itemWidth * 10;
// Swipe left
if (newOffset < totalWidth * 0.5) {
newOffset += totalWidth;
// Swipe right
} else if (newOffset > totalWidth * 1.5) {
newOffset -= totalWidth
}
this.rollOffset = newOffset
return { offsetRemain: newOffset - currOffset }
})
}
}
}
}
The horizontal looping scrolling effect is as follows:


Top comments (0)