Read the original article:How to achieve the automatic scrolling effect of the list.
How to achieve the automatic scrolling effect of the list.
Requirement Description
Users wants to achieve both horizontal and vertical scrolling.
Background Knowledge
Auto-scroll in ArkUI is achieved by updating the Scroller offset.
Both directions use the same scrollTo logic with different axes.
Implementation Steps
The automatic scrolling effect is achieved by looping this.scroller.scrollTo through the setInterval timer.
startAutoRoll() {
// Make sure the offset interval is 10 milliseconds
this.rollOffset = this.rollOffset - this.rollOffset % 2
this.IntervalNum = setInterval(() => {
this.rollOffset += 1
if (this.rollOffset % this.itemHeight === 0) {
// Data is subtracted before and added after
this.data.deleteData(0)
this.data.pushData(this.nextNum.toString())
if (this.nextNum === 9) {
this.nextNum = 0
} else {
this.nextNum++
}
// Corresponding to data changes to prevent exceeding
this.rollOffset -= this.itemHeight
this.scroller.scrollTo({ xOffset: 0, yOffset: this.rollOffset, animation: false })
} else {
// Slide animation
this.scroller.scrollTo({
xOffset: 0,
yOffset: this.rollOffset,
animation: { duration: 10, curve: Curve.Linear }
})
}
}, 10)
}
Calculate the actual required scrolling amount in the onScrollFrameBegin callback of the List and return it as the return value of the event handling function. The List will scroll according to the actual scrolling amount of the return value.
.onScrollFrameBegin((offset: number, state: ScrollState) => {
let currOffset = this.scroller.currentOffset().yOffset;
let newOffset = currOffset + offset;
let totalHeight = this.itemHeight * 10;
// Slide up
if (newOffset < totalHeight * 0.5) {
newOffset += totalHeight;
// Decline
} else if (newOffset > totalHeight * 1.5) {
newOffset -= totalHeight
}
this.rollOffset = newOffset
return { offsetRemain: newOffset - currOffset }
})
Code Snippet / Configuration
The complete code example is as follows:
1.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 = 60
startAutoRoll() {
// Make sure the offset interval is 10 milliseconds
this.rollOffset = this.rollOffset - this.rollOffset % 2
this.IntervalNum = setInterval(() => {
this.rollOffset += 1
if (this.rollOffset % this.itemHeight === 0) {
// Data is subtracted before and added after
this.data.deleteData(0)
this.data.pushData(this.nextNum.toString())
if (this.nextNum === 9) {
this.nextNum = 0
} else {
this.nextNum++
}
// Corresponding to data changes to prevent exceeding
this.rollOffset -= this.itemHeight
this.scroller.scrollTo({ xOffset: 0, yOffset: this.rollOffset, animation: false })
} else {
// Slide animation
this.scroller.scrollTo({
xOffset: 0,
yOffset: this.rollOffset,
animation: { duration: 10, curve: Curve.Linear }
})
}
}, 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) {
List({ scroller: this.scroller }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Column() {
Text('TEST.' + item.toString())
.fontSize(16)
.textAlign(TextAlign.Center)
Row() {
Text('Get a reply in 20 seconds')
.fontSize(13)
.textAlign(TextAlign.Center)
.fontColor('#ffa933ba')
Row() {
Text('TEST')
.fontSize(13)
.textAlign(TextAlign.Center)
.fontColor('#ff656266')
Image($r('app.media.startIcon'))
.width(12)
.height(12)
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
}
.alignItems(HorizontalAlign.Center)
.width('100%')
}
.onClick(() => {
console.info('index:', item)
})
.borderRadius(10)
.backgroundColor('#FFFFFF')
.height(40)
.width('90%')
.margin({
left: '2%',
right: '2%',
top: this.itemHeight * 0.1,
bottom: this.itemHeight * 0.1
})
}, (item: string) => item)
}
.scrollBar(BarState.Off)
.width('100%')
.layoutWeight(1)
.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;
// Slide up
if (newOffset < totalHeight * 0.5) {
newOffset += totalHeight;
// Decline
} else if (newOffset > totalHeight * 1.5) {
newOffset -= totalHeight
}
this.rollOffset = newOffset
return { offsetRemain: newOffset - currOffset }
})
}.height('100%')
.hideTitleBar(true)
}
}
2.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);
})
}
}
3.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);
}
}
Test Results
Tested on HarmonyOS 5.1.0(18) emulator - works as expected
Limitations or Considerations
Not applicable to apps targeting API Level < 11.
Related Documents or Link
https://developer.huawei.com/consumer/en/doc/harmonyos-references/ts-rendering-control-lazyforeach
https://developer.huawei.com/consumer/en/doc/harmonyos-references/ts-container-scroll#onscrollstart9
https://developer.huawei.com/consumer/en/doc/harmonyos-references/ts-container-scroll#onscrollstop9

Top comments (0)