HarmonyOS鸿蒙Next中使用ListView列表实现左右双向滑动效果
HarmonyOS鸿蒙Next中使用ListView列表实现左右双向滑动效果
使用ListView 列表实现下拉刷新,上拉加载,左右双向滑动效果(需同步列表下面左右效果)有没有好的实现方式(网络请求)
开发者您好,可查看如下方案:
【解决方案】
开发者您好,下拉刷新上拉加载可通过PullToRefresh实现(这是一款OpenHarmony环境下可用的下拉刷新、上拉加载组件),左右双向滑动可通过List组件和Scroll组件构建页面布局,结合PanGesture手势滑动事件实现滑动效果。参考示例如下:
- 在目录entry/src/oh-package.json5文件中配置PullToRefresh依赖:
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"[@ohos](/user/ohos)/pulltorefresh": "^2.1.2"
}
}
- 示例代码如下:
// Index.ets
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { PullToRefresh } from '[@ohos](/user/ohos)/pulltorefresh';
interface IndustryInfo {
industryName: string;
datas: StocksInfo;
}
interface StocksInfo {
id: string;
increase: string;
limit: number;
score: number;
buy: string;
funds: string;
quantity: string;
growth: string;
board: number;
leading: number;
}
@Entry
@Component
struct Index {
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left | PanDirection.Right });
@Provide offsetX: number | undefined = undefined;
aboutToAppear(): void {
let windowClass: window.Window | undefined = undefined;
window.getLastWindow(this.getUIContext()?.getHostContext(), (err: BusinessError, data) => {
windowClass = data;
let SystemBarProperties: window.SystemBarProperties = {
statusBarColor: '#F1F3F5',
navigationBarColor: '#000'
};
try {
let promise = windowClass.setWindowSystemBarProperties(SystemBarProperties);
promise.then(() => {
console.info('Succeeded in setting the system bar properties.');
}).catch((err: BusinessError) => {
console.error(`Failed to set the system bar properties. Cause code: ${err.code}, message: ${err.message}`);
});
} catch (exception) {
console.error(`Failed to set the system bar properties. Cause code: ${exception.code}, message: ${exception.message}`);
}
});
}
build() {
Column() {
Column() {
MainArea();
}
.backgroundColor('#fff')
.padding(12)
.gesture(
// 用于触发拖动手势事件,滑动的最小距离为5vp时拖动手势识别成功。
PanGesture(this.panOption).onActionStart((event?: GestureEvent) => {
this.offsetX = event?.offsetX;
console.info('gesture11: PanGesture start' + event?.offsetX);
})
)
.borderRadius(24);
}
.height('100%')
.width('100%')
.padding(10)
.backgroundColor('#F1F3F5')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]);
}
}
@Component
struct tableRow {
@Prop infos: IndustryInfo;
build() {
Flex({ alignItems: ItemAlign.Center }) {
Text(this.infos.industryName).fontSize(16).width(86).textAlign(TextAlign.Start);
ScrollComponent({ stock: this.infos.datas });
}.width('100%');
}
}
@Component
struct ScrollComponent {
scroller: Scroller = new Scroller();
@Prop stock: StocksInfo;
@Consume @Watch('onCountUpdated') offsetX: number;
// 监听滑动距离
onCountUpdated() {
if (this.offsetX) {
this.scroller.scrollTo({
xOffset: this.scroller.currentOffset()?.xOffset - this.offsetX * 50, // 滑动距离可根据实际情况设置
yOffset: 0,
animation: { duration: 1000, curve: Curve.Ease }
});
}
}
build() {
Scroll(this.scroller) {
Row() {
Text(this.stock.increase).fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.limit + '').fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.score + '').fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.buy).fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.funds).fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.quantity).fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.growth).fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.board + '').fontSize(16).width(86).textAlign(TextAlign.Center);
Text(this.stock.leading + '').fontSize(16).width(86).textAlign(TextAlign.Center);
}.padding({ top: 4, bottom: 4 });
}
.scrollable(ScrollDirection.Horizontal) // 滚动方向
.scrollBar(BarState.Off) // 滚动条常驻显示
.edgeEffect(EdgeEffect.None)
.width('100%')
// 禁止scroll滚动
.enabled(false);
}
}
@Component
struct MainArea {
private scroller: Scroller = new Scroller();
private fullScroller: Scroller = new Scroller();
@State private arr: IndustryInfo[] = [
{
industryName: '乳制品',
datas: {
id: '10001',
increase: '5%',
limit: 1,
score: 49,
buy: '1.01亿',
funds: '1.60亿',
quantity: '0.6%',
growth: '2073万',
board: 3,
leading: 102
}
}
];
private settings: string[] = ['涨幅', '涨停数', 'AI得分', '主动净买', '主力资金', '量比', '涨速', '连板', '龙头股'];
@Consume @Watch('onCountUpdated') offsetX: number;
pushInfo(info: IndustryInfo): IndustryInfo {
let indusInfo: IndustryInfo = {
industryName: '乳制品',
datas: {
id: '10001',
increase: '5%',
limit: 1,
score: 49,
buy: '1.01亿',
funds: '1.60亿',
quantity: '0.6%',
growth: '2073万',
board: 3,
leading: 102
}
}
indusInfo.datas.limit += info.datas.limit
return indusInfo
}
aboutToAppear(): void {
for (let i = 1; i <= 30; i++) {
let info = this.arr[this.arr.length-1]
this.arr.push(this.pushInfo(info))
}
}
@Builder
private getListView() {
List({ scroller: this.scroller }) {
ForEach(this.arr, (item: IndustryInfo, index: number) => {
ListItem() {
Column() {
tableRow({ infos: item }).padding(4);
if (index !== this.arr.length - 1) {
Divider()
.strokeWidth(1)
.width("100%")
.color('#eee');
}
};
};
});
}.sticky(StickyStyle.Header)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
.backgroundColor('#ffffffff')
.edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
}
@Builder
tabContentData() {
Column() {
PullToRefresh({
data: this.arr,
scroller: this.scroller,
customList: () => {
this.getListView();
},
// 可选项,下拉刷新回调
onRefresh: () => {
return new Promise<string>((resolve) => {
// 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
setTimeout(() => {
resolve('刷新成功');
let info = this.arr[this.arr.length-1]
this.arr.push(this.pushInfo(info))
}, 500);
});
},
// 可选项,上拉加载更多回调
onLoadMore: () => {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve('');
let info = this.arr[this.arr.length-1]
this.arr.push(this.pushInfo(info))
}, 2000);
});
},
customLoad: null,
customRefresh: null,
});
};
}
// table header
@Builder
tabHeader() {
Column() {
Flex({ alignItems: ItemAlign.Center }) {
Text('设置')
.fontSize(16)
.width(86)
.padding({ bottom: 4 })
.textAlign(TextAlign.Start);
Scroll(this.fullScroller) {
Row() {
ForEach(this.settings, (setting: string) => {
Text(setting)
.fontSize(16)
.width(86)
.textAlign(TextAlign.Center);
});
};
}
.enabled(true)
.scrollable(ScrollDirection.Horizontal) // 滚动方向横向
.width('100%')
.padding({ bottom: 4 })
.scrollBar(BarState.Off) // 滚动条常驻显示
.edgeEffect(EdgeEffect.None)
// 禁止scroll滚动
.enabled(false);
}
.padding(4);
Divider()
.strokeWidth(1)
.width("100%")
.color('#eee');
}.width('100%');
}
// 监听滑动距离
onCountUpdated() {
if (this.offsetX) {
this.scroller.scrollTo({
xOffset: this.scroller.currentOffset()?.xOffset - this.offsetX * 50, // 滑动距离可根据实际情况设置
yOffset: 0,
animation: { duration: 1000, curve: Curve.Ease }
});
this.fullScroller.scrollTo({
xOffset: this.fullScroller.currentOffset()?.xOffset - this.offsetX * 50, // 滑动距离可根据实际情况设置
yOffset: 0,
animation: { duration: 1000, curve: Curve.Ease }
});
}
}
build() {
Column() {
this.tabHeader()
this.tabContentData()
}
}
}
【背景知识】
-
PullToRefresh是一款OpenHarmony环境下可用的下拉刷新、上拉加载组件。支持设置内置动画的各种属性,支持设置自定义动画,支持lazyForEach的数据作为数据源。
PullToRefresh使用限制:
1、目前只支持List、Scroll、Tabs、Grid和WaterFlow系统容器组件;
2、暂不支持设置系统容器组件的弹簧效果和阴影效果,使用时需要将系统组件edgeEffect属性的值设置为(EdgeEffect.None);
3、暂不支持页面触底时自动触发上拉加载功能;
4、暂不支持在页面数据不满一屏时触发上拉加载功能;
5、暂不支持通过代码的方式去触发下拉刷新功能;
6、暂不支持在下拉刷新动画结束时提供手势结束的回调。
-
Refresh:可以进行页面下拉操作并显示刷新动效的容器组件。
-
List组件:列表包含一系列相同宽度的列表项。
-
Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的尺寸时,内容可以滚动。
-
PanGesture:滑动手势事件,当滑动的最小距离达到设定的最小值时触发滑动手势事件。
更多关于HarmonyOS鸿蒙Next中使用ListView列表实现左右双向滑动效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
List组件外面包一个Refresh组件,然后用List组件的onReachEnd回调做触底加载更多,用Refresh组件做下拉刷新。左右滑动的话,就用这个:#示例4通过componentcontent设置划出组件
列表的标题跟item滑动同步
比如横向排列有十多个已经超出屏幕,需左右滑动查看
你说的效果是不是像华为浏览器搜索框下面的标题栏效果?那样的话可以用ListItem嵌套横向Scroll组件,foreach循环渲染,
类似股票软件效果,
在HarmonyOS Next中,使用ArkUI的List组件实现左右双向滑动效果,需通过List的scrollBar属性设置为BarState.On开启滚动条,并配合布局方向控制。主要步骤包括:
- 创建List组件,设置其布局方向为水平(orientation: Axis.Horizontal)。
- 在List内使用ListItem子组件,每个ListItem可放置一个Row或自定义组件,实现横向排列。
- 通过手势事件或滑动监听,可进一步自定义滑动交互。此方法基于ArkTS/ArkUI框架,不涉及Java或C语言。
在HarmonyOS Next中,要实现ListView的左右双向滑动效果,可以结合使用SwipeController和ListItem的滑动能力。以下是核心实现思路:
-
使用SwipeController:
- 为ListView的每个ListItem配置SwipeController,通过
swipeAction方法定义左右滑动的视图和操作。 - 示例代码:
class MySwipeController implements SwipeController { leftActions(): SwipeActionItem[] { return [{ action: () => { /* 左滑操作 */ }, builder: () => { /* 左滑视图 */ } }]; } rightActions(): SwipeActionItem[] { return [/* 右滑操作配置 */]; } }
- 为ListView的每个ListItem配置SwipeController,通过
-
同步列表下方效果:
- 若需同步列表下方的左右滑动(如导航指示器),可在滑动回调中更新状态,并通过状态变量控制下方视图的偏移量。
- 使用
Scroll或List的onScroll事件监听滑动位置,动态计算并应用平移效果。
-
结合下拉刷新和上拉加载:
- 使用
Refresh和LoadingProgress组件包裹ListView,分别实现下拉刷新和上拉加载。 - 网络请求在刷新/加载回调中触发,更新数据后刷新列表。
- 使用
-
注意事项:
- 左右滑动需合理设置滑动阈值,避免与垂直滚动冲突。
- 列表性能优化:对于长列表,建议使用
LazyForEach加载数据。
此方案能有效实现双向滑动与数据加载的同步,且符合HarmonyOS Next的声明式开发范式。

