用户3116712
用户3116712
  • 发布:2026-10-14 10:16
  • 更新:2026-10-14 10:16
  • 阅读:13

如何通过List组件实现可自动循环滚动的列表

分类:HBuilder

问题现象
如何通过List组件实现一个既能自动滚动又能无限循环的列表?要求列表需要具备自动滚动功能,并能够实现无缝循环播放的效果,使首尾衔接自然,不出现空白或卡顿现象。

背景知识
List组件是可滚动的容器组件列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
setInterval定时器可以重复调用一个函数,在每次调用之间可以设置固定的时间延迟。
ListScroller的scrollTo函数,可以控制List的滚动。
解决方案
场景一:在纵向实现循环滑动列表:
通过setInterval定时器循环执行this.scroller.scrollTo达到自动滚动的效果。具体逻辑为:当移动大于五行时,每次执行data数组删除索引为0数据,将该数据加到数组末尾的逻辑,实现循环。
startAutoRoll() {
this.last = new Date().getTime();
this.intervalNum = setInterval(() => {
if (this.rollOffset > (this.itemWidth 5)) {
for (let i = 0; i < 5; i++) {
// data数组前减后加,实现循环
this.data.deleteData(0);
this.data.pushData(this.nextNum.toString());
if (this.nextNum === 9) {
this.nextNum = 0;
} else {
this.nextNum++;
}
}
// 对应data变化,防止超出
this.rollOffset -= this.itemWidth
5;
}
let curr = new Date().getTime();
this.rollOffset += 0.5 (curr - this.last) / 10;
// 改为x轴移动
this.scroller.scrollTo({ xOffset: this.rollOffset, yOffset: 0, animation: false });
this.last = curr;
}, 10);
}
在List的onScrollFrameBegin回调里计算实际需要的滚动量并作为事件处理函数的返回值返回,List将按照返回值的实际滚动量进行滚动。
.onScrollFrameBegin((offset: number) => {
let currOffset = this.scroller.currentOffset().xOffset; // 改为x轴
let newOffset = currOffset + offset;
let totalWidth = this.itemWidth
10;
// 左滑
if (newOffset < totalWidth 0.5) {
newOffset += totalWidth;
// 右滑
} else if (newOffset > totalWidth
1.5) {
newOffset -= totalWidth;
}
this.rollOffset = newOffset;
return { offsetRemain: newOffset - currOffset };
})
完整示例参考如下:

VerticalCircularList.ets:

import { MyDataSource } from './MyDataSource';

@Entry
@Component
struct VerticalCircularList {
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++) {
// data数组前减后加,实现循环
this.data.deleteData(0);
this.data.pushData(this.nextNum.toString());
if (this.nextNum === 9) {
this.nextNum = 0;
} else {
this.nextNum++;
}
}
// 对应data变化,防止超出
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);
}

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('宝宝吃完奶后,怎样让他快速入睡。' + item.toString())
.fontSize(12)
.textAlign(TextAlign.Start)
.margin({ left: 8 });
Row() {
Text('20秒获得回复')
.fontSize(10)
.textAlign(TextAlign.Start)
.fontColor('#ffa933ba')
.margin({ left: 8, bottom: 8 });
Row() {
Text('1位妈妈正在回答')
.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%');
}
.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) => {
let currOffset = this.scroller.currentOffset().yOffset;
let newOffset = currOffset + offset;
let totalHeight = this.itemHeight 10;
// 上滑
if (newOffset < totalHeight
0.5) {
newOffset += totalHeight;
// 下滑
} 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);
}
}
纵向循环滚动效果预览:

点击放大

场景二:在横向实现循环滚动列表。横向滑动与纵向类似,完整代码如下(BasicDataSource.ets和MyDataSource.ets与场景一相同)。
import { MyDataSource } from './MyDataSource';

@Entry
@Component
struct HorizontalCircularList {
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; // 纵向中的itemHeight=50改为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++) {
// data数组前减后加,实现循环
this.data.deleteData(0);
this.data.pushData(this.nextNum.toString());
if (this.nextNum === 9) {
this.nextNum = 0;
} else {
this.nextNum++;
}
}
// 对应data变化,防止超出
this.rollOffset -= this.itemWidth
5;
}
let curr = new Date().getTime();
this.rollOffset += 0.5 * (curr - this.last) / 10;
// 改为x轴移动
this.scroller.scrollTo({ xOffset: this.rollOffset, yOffset: 0, animation: false });
this.last = curr;
}, 10);
}

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('宝宝吃完奶后,怎样让他快速入睡。' + item.toString())
.fontSize(12)
.textAlign(TextAlign.Start)
.margin({ left: 8 })
Row() {
Text('20秒获得回复')
.fontSize(10)
.textAlign(TextAlign.Start)
.fontColor('#ffa933ba')
.margin({ left: 8, bottom: 8 })
Row() {
Text('1位妈妈正在回答')
.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%')
}
.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) // 滑动方向改为横向
.scrollSnapAlign(ScrollSnapAlign.NONE)
.friction(0.5)
.onScrollStart(() => {
clearInterval(this.intervalNum);
})
.onScrollStop(() => {
this.startAutoRoll();
})
.onScrollFrameBegin((offset: number) => {
let currOffset = this.scroller.currentOffset().xOffset; // 改为x轴
let newOffset = currOffset + offset;
let totalWidth = this.itemWidth 10;
// 左滑
if (newOffset < totalWidth
0.5) {
newOffset += totalWidth;
// 右滑
} else if (newOffset > totalWidth * 1.5) {
newOffset -= totalWidth;
}
this.rollOffset = newOffset;
return { offsetRemain: newOffset - currOffset };
})
}
}
}
}
横向循环滚动效果预览:
https://pastebin.com/HJE2dhLq
https://pastebin.com/nkzCn7HV
https://pastebin.com/uA90CJJ2
https://pastebin.com/4C4fed7e
https://pastebin.com/0BSBVXwe
https://pastebin.com/eTuV1Fnn
https://pastebin.com/zXfTzUAq
https://pastebin.com/rxNH5fcy
https://pastebin.com/wWhTKS70
https://pastebin.com/c2K8mBcD
https://pastebin.com/E5kLD72k
https://pastebin.com/94hng3FK
https://pastebin.com/01d1KkZW
https://pastebin.com/d3FYwhEg
https://pastebin.com/ZMj8Hvk7
https://pastebin.com/uWjMtDx8
https://pastebin.com/iHaiz2L7
https://pastebin.com/z2BPWdxp
https://pastebin.com/LbZidctc
https://pastebin.com/LBG8eLMt
https://pastebin.com/hFq7eHwj
https://pastebin.com/AkQbBpE7
https://pastebin.com/4waQFKZM
https://pastebin.com/UZeW3ZVC
https://pastebin.com/7CViWBt3
https://pastebin.com/quVPK28R
https://pastebin.com/dBTSAVby
https://pastebin.com/WuQfhHJ8
https://pastebin.com/23CqskTz
https://pastebin.com/QMrmmJi3
https://pastebin.com/CbnuNEym
https://pastebin.com/ifR7hFdP
https://pastebin.com/xsRZ9Aen
https://pastebin.com/jE5zDDKt
https://pastebin.com/WhMrQvAf
https://pastebin.com/GctdrDnH
https://pastebin.com/Pxwn9wdc
https://pastebin.com/Q5EvVD0T
https://pastebin.com/XrbD0dEx
https://pastebin.com/CQV23aU4
https://pastebin.com/pWFR7y5h
https://pastebin.com/biV0Mun2
https://pastebin.com/eWQgK2RP
https://pastebin.com/4YMvSRMH
https://pastebin.com/xDVMvZyS
https://pastebin.com/KHcJHWrx
https://pastebin.com/fZqyvvdF
https://pastebin.com/YfiyS9Bm
https://pastebin.com/TwZNsSCn
https://pastebin.com/EE2fQaj1
https://pastebin.com/8AnPUCRn
https://pastebin.com/GBuu5Gsx
https://pastebin.com/5g0gMSSy
https://pastebin.com/ei0YbVx6
https://pastebin.com/yLyR55i4
https://pastebin.com/Hp8yFgVw
https://pastebin.com/zHimMn78
https://pastebin.com/739vUMNE
https://pastebin.com/bqBwypSa
https://pastebin.com/cPgjd42j
https://pastebin.com/stvfDL4e
https://pastebin.com/brfkatzP
https://pastebin.com/idYbBndt
https://pastebin.com/KsEe2PsH
https://pastebin.com/fpj6KTJ2
https://pastebin.com/cEpQrjKt
https://pastebin.com/DaA7V3Ve
https://pastebin.com/DfAgfcTv
https://pastebin.com/fmiWSApJ
https://pastebin.com/JMGgMqKG
https://pastebin.com/L26CZ9mf
https://pastebin.com/tK4qR9tc
https://pastebin.com/PW997KUV
https://pastebin.com/aYwj8wex
https://pastebin.com/k0XUpgUM
https://pastebin.com/TWDNKt0Q
https://pastebin.com/NitfX8AR
https://pastebin.com/dfkVrTA7
https://pastebin.com/2dwN8fxS
https://pastebin.com/fRS4ueve
https://pastebin.com/rghUtELm
https://pastebin.com/74kicDaN
https://pastebin.com/EEAXdqNk
https://pastebin.com/4RWAErvg
https://pastebin.com/yxaNT9EF
https://pastebin.com/HBZFhBtb
https://pastebin.com/eDwdcPDu
https://pastebin.com/VX1UqWzA
https://pastebin.com/JqE8mWem
https://pastebin.com/9H5BzE92
https://pastebin.com/zpPs1KVE
https://pastebin.com/5HHfyF38
https://pastebin.com/xFxN7eWd
https://pastebin.com/80DYkz7u
https://pastebin.com/gAh3HaPw
https://pastebin.com/ahmpvAnZ
https://pastebin.com/Jn2Th6xf
https://pastebin.com/DKjcxw81
https://pastebin.com/yk7rSWu9
https://pastebin.com/fcUg8ZQk
https://pastebin.com/a1r1PCdJ
https://pastebin.com/FhCi1QJu
https://pastebin.com/5fFryXC8
https://pastebin.com/Wz7mAnJf
https://pastebin.com/TuwVGqNm
https://pastebin.com/LpwKYbuD
https://pastebin.com/u7NBkj9J
https://pastebin.com/mp65yiwL
https://pastebin.com/xKYNVUyp
https://pastebin.com/WCK2WCcQ
https://pastebin.com/6aD3By5H
https://pastebin.com/FN533X5H
https://pastebin.com/0k3J2yjy
https://pastebin.com/mnBDeL4S
https://pastebin.com/hJTmgAjh
https://pastebin.com/vAY5SEW0
https://pastebin.com/eC1AMBVG
https://pastebin.com/42zCfRbd
https://pastebin.com/fLK20Nh0
https://pastebin.com/mEKtUrqa
https://pastebin.com/5b4A711Z
https://pastebin.com/LT0B39kr
https://pastebin.com/PCkp5kjW
https://pastebin.com/wMkZRPXj
https://pastebin.com/BDWVBGiT
https://pastebin.com/C54cCyfS
https://pastebin.com/0BEPLPwb
https://pastebin.com/tDb94K7m
https://pastebin.com/gQry9mPV
https://pastebin.com/XUnxKXGQ
https://pastebin.com/wzi46qp4
https://pastebin.com/ARrL4JcM
https://pastebin.com/D713Mmax
https://pastebin.com/f4jpA0F3
https://pastebin.com/6iAUbJJD
https://pastebin.com/2M9QMQJa
https://pastebin.com/R88jLqiv
https://pastebin.com/6Fr5ztq6
https://pastebin.com/vUPSiDLQ
https://pastebin.com/J6VnaSxY
https://pastebin.com/3qw5J8VH
https://pastebin.com/P5qU1Gsi
https://pastebin.com/Pdqhw1Ei
https://pastebin.com/7QpAdNLz
https://pastebin.com/PPne67Vq
https://pastebin.com/CWbuQr87
https://pastebin.com/UC3tfF8i
https://pastebin.com/Td727iSx
https://pastebin.com/Q8Fbf5NF
https://pastebin.com/8BsnuYCz
https://pastebin.com/fJFT4Fs2
https://pastebin.com/b3Yt91Af
https://pastebin.com/ewNQursJ
https://pastebin.com/aaf9XKYj
https://pastebin.com/tixgQY61
https://pastebin.com/S0USyNcE
https://pastebin.com/MTYexN4E
https://pastebin.com/d2ZQ2yQh
https://pastebin.com/znUX2fZh
https://pastebin.com/ZWh0WpAU
https://pastebin.com/NMAzp9Ch
https://pastebin.com/ua0eJ16V
https://pastebin.com/5Tf5qPYb
https://pastebin.com/FMYU1RiY
https://pastebin.com/wyN1ngaV
https://pastebin.com/cDAij0bQ
https://pastebin.com/kWbMQdFU
https://pastebin.com/ShEn0GsW
https://pastebin.com/dnvQjfub
https://pastebin.com/nAJpYemV
https://pastebin.com/wACBxcqj
https://pastebin.com/2YzTQh7f
https://pastebin.com/Q4NZcAup
https://pastebin.com/4FY8ZrvX
https://pastebin.com/eFfY2MsC
https://pastebin.com/TqCfehCD
https://pastebin.com/T5gK9bXf
https://pastebin.com/nACFMezq
https://pastebin.com/T4kZMV37
https://pastebin.com/iWsqsCks
https://pastebin.com/icsN3cNV
https://pastebin.com/uABx78tN
https://pastebin.com/7jJax0bE
https://pastebin.com/s15Qp85s
https://pastebin.com/G30uPeeN
https://pastebin.com/UdGzczSf
https://pastebin.com/V1AQrrsb
https://pastebin.com/Ltd21LYs
https://pastebin.com/x9feTVzf
https://pastebin.com/8fXWqLdG
https://pastebin.com/nZHv0u2z
https://pastebin.com/yWn9f5QX
https://pastebin.com/uCeCwwP6
https://pastebin.com/yA9hgAWy
https://pastebin.com/tyF1vK5b
https://pastebin.com/wm4RmEXz
https://pastebin.com/XSih3uDc
https://pastebin.com/4FaxC9FT
https://pastebin.com/iVpusqPn
https://pastebin.com/29FuabAp
https://pastebin.com/MfNVg2Hy

0 赞 关注 分享

要回复文章请先登录或注册