鸿蒙新特性——Swiper 轮播滑动组件详解
一、引言
滑动切换是移动端中最自然的交互方式之一。App 的引导页逐屏滑动展示新功能,电商首页的 Banner 自动轮播推广商品,相册中左右滑动浏览照片——这些场景背后都有一个共同的组件需求:在有限的屏幕空间内,让多个内容页面可以水平或垂直滑动切换。
传统实现轮播需要组合 Scroll 容器 + 手势检测 + 自动播放定时器 + 指示器 UI + 动画控制,代码量大且细节繁琐:惯性滚动的距离计算、边界回弹效果、自动播放和手动滑动的冲突处理、循环模式的索引映射——每一项单独看都不难,但合在一起就是一个不小的工程。
HarmonyOS 提供了 Swiper 组件——一个功能完备的轮播滑动容器。开发者只需将子组件放入 Swiper,剩下的滑动切换、自动播放、循环模式、指示器、导航箭头等全部由 Swiper 内置实现。它支持圆点指示器(DotIndicator)和数字指示器(DigitIndicator),支持横向/纵向两个方向,支持自定义动画曲线、时长和切换效果。
本文通过一个 App 新功能引导页 Demo 深入讲解 Swiper 组件的核心用法:如何创建多个滑动页面?如何配置自动播放和循环?如何使用圆点和数字指示器?如何显示导航箭头?如何通过 SwiperController 控制页面跳转?
阅读完本文,你将能够:
- 使用 Swiper 组件创建引导页、Banner 轮播等滑动切换界面
- 使用
autoPlay/interval/loop控制自动播放行为 - 使用
DotIndicator和DigitIndicator定制指示器样式 - 使用
displayArrow显示导航箭头 - 使用
SwiperController程序化控制页面跳转 - 通过
onChange回调监听页面切换事件
二、Swiper 组件 API 总览
2.1 构造函数
Swiper(controller?: SwiperController)
Swiper 的构造函数接收一个可选的 SwiperController 参数,用于程序化控制页面跳转:
private swiperController: SwiperController = new SwiperController();
SwiperController 提供三个方法:
| 方法 | 用途 |
|---|---|
showNext() |
切换到下一个子页面 |
showPrevious() |
切换到上一个子页面 |
changeIndex(index, useAnimation?) |
切换到指定索引的页面 |
2.2 核心方法一览
Swiper 的方法分为三类:播放控制、UI 配置、事件回调。
播放控制方法:
| 方法 | 用途 | 示例 |
|---|---|---|
index(value: number) |
设置/获取当前页索引 | .index(this.currentIdx) |
autoPlay(value: boolean) |
开启/关闭自动播放 | .autoPlay(true) |
interval(value: number) |
自动播放间隔(毫秒) | .interval(3000) |
loop(value: boolean) |
开启/关闭无限循环 | .loop(true) |
duration(value: number) |
切换动画时长(毫秒) | .duration(400) |
vertical(value: boolean) |
是否纵向滑动 | .vertical(false) |
disableSwipe(value: boolean) |
禁用手动滑动 | .disableSwipe(false) |
UI 配置方法:
| 方法 | 用途 |
|---|---|
indicator(value) |
配置指示器(DotIndicator/DigitIndicator/boolean) |
displayArrow(value) |
显示/隐藏导航箭头 |
displayMode(value) |
显示模式(单页/多页) |
itemSpace(value) |
子页面之间的间距 |
cachedCount(value) |
预缓存页面数量 |
effectMode(value) |
边缘效果(弹簧/无效果) |
curve(value) |
动画曲线 |
事件回调方法:
| 方法 | 触发时机 | 回调参数 |
|---|---|---|
onChange(callback) |
页面切换完成时 | (index: number) => void |
onAnimationStart(callback) |
切换动画开始时 | (index: number) => void |
onAnimationEnd(callback) |
切换动画结束时 | (index: number) => void |
onGestureSwipe(callback) |
手势滑动时 | (index: number) => void |
2.3 指示器:DotIndicator vs DigitIndicator
Swiper 支持两种内置指示器,通过 indicator() 方法配置:
DotIndicator(圆点指示器)——默认样式,适合少量页面(2~10 个):
Swiper() { ... }
.indicator(
new DotIndicator()
.itemWidth(8) // 圆点宽度
.itemHeight(8) // 圆点高度
.selectedItemWidth(16) // 选中圆点宽度(长条效果)
.color('#FFFFFF44') // 未选中圆点颜色
.selectedColor('#FFFFFF') // 选中圆点颜色
)
DotIndicator 的 selectedItemWidth 设置为大于 itemWidth 时,选中项会呈现"长条"形态——这是引导页和 Banner 的常用设计。
DigitIndicator(数字指示器)——适合页面较多(>10 个)的场景,显示为 “1/5” 格式:
Swiper() { ... }
.indicator(
new DigitIndicator()
.fontColor('#FFFFFFAA') // 未选中字体颜色
.selectedFontColor('#FFFFFF') // 选中字体颜色
.fontSize(14) // 字体大小
)
如果不需要指示器,传入 false 即可:.indicator(false)。
2.4 基本用法模式
@State currentIndex: number = 0;
private controller: SwiperController = new SwiperController();
Swiper(this.controller) {
ForEach(this.pages, (page: PageItem) => {
Text(page.title)
})
}
.index(this.currentIndex)
.autoPlay(true)
.interval(3000)
.loop(true)
.duration(400)
.indicator(new DotIndicator().color('#FFFFFF44').selectedColor('#FFFFFF'))
.onChange((index: number) => { this.currentIndex = index; })
使用模式的关键点:
index()绑定 @State 变量,在onChange中更新,保持两者同步autoPlay和interval配合使用,只设 autoPlay 不设 interval 时为默认 3 秒loop(true)允许从最后一页无缝切换到第一页ForEach渲染子页面,支持条件渲染(如最后一页显示特殊按钮)
2.5 displayArrow——导航箭头
displayArrow 在 Swiper 两侧显示箭头按钮,方便用户在不滑动的情况下切换页面:
Swiper() { ... }
.displayArrow({
showBackground: true, // 显示箭头背景
backgroundSize: 28, // 背景尺寸(vp)
arrowColor: '#FFFFFF', // 箭头颜色
backgroundColor: '#00000030' // 箭头背景颜色
})
如果不需要箭头,传入 false:.displayArrow(false)。对于引导页等场景,箭头通常是关闭的(让用户自己滑动);对于 Banner 轮播,箭头可以与自动播放共存。
2.6 loop 循环模式的原理
loop(true) 开启无限循环——用户滑动到最后一页后继续向前滑动,会回到第一页;自动播放到最后一页后等待 interval 时间,自动跳回第一页。
注意事项:
- loop 模式下,Swiper 内部会创建额外的"辅助页"来实现无缝过渡
- 当
cachedCount较小时,极端快速的连续滑动可能看到短暂的空白(通过增大 cachedCount 缓解) - loop 与
displayCount > 1模式下需要确保子页面总数 >= displayCount + 2
三、Demo 设计:App 新功能引导页
3.1 功能概述
Demo 是一个 App 新功能引导页,模拟 App 首次启动时展示新功能介绍的经典交互:
- 4 个引导页面:每个页面有不同的 emoji 图标、标题、描述和背景色,最后一页有"立即开始体验"按钮
- 自动播放:默认每 3 秒自动切换到下一页
- 控制面板:5 个可配置项——自动播放开关、无限循环开关、圆点/数字指示器切换、导航箭头开关、轮播间隔调节(1s ~ 8s,每 500ms 一档)
- 状态展示:实时显示当前页、自动播放状态、循环模式、指示器类型和轮播间隔
所有 4 个交互配置项均可独立切换,实时反映在 Swiper 的行为上。
3.2 页码数据定义
interface PageItem {
title: string;
desc: string;
color: string;
emoji: string;
}
private pages: PageItem[] = [
{ title: '智能家居', desc: '一键掌控全屋设备...', color: '#1677FF', emoji: '🏠' },
{ title: '健康管理', desc: '步数统计、心率监测...', color: '#52C41A', emoji: '💚' },
{ title: '效率工具', desc: '待办清单、日程提醒...', color: '#FF9800', emoji: '⚡' },
{ title: '即刻开始', desc: '所有功能已就绪...', color: '#E91E63', emoji: '🚀' }
];
使用 interface 显式声明 PageItem 类型——ArkTS 不允许匿名的内联类型标注,独立 interface 是规范做法。
3.3 Swiper 配置
Swiper(this.swiperController) {
ForEach(this.pages, (page: PageItem, idx: number) => {
Column() {
Text(page.emoji).fontSize(64).margin({ bottom: 16 })
Text(page.title).fontSize(22).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text(page.desc).fontSize(14).fontColor('#FFFFFFCC').textAlign(TextAlign.Center)
if (idx === this.pages.length - 1) {
Button('🚀 立即开始体验')
.fontSize(16)
.fontColor(page.color)
.backgroundColor('#FFFFFF')
.borderRadius(24)
.height(48)
.onClick(() => { this.swiperController.changeIndex(0); })
}
}
.width('100%')
.height(320)
.justifyContent(FlexAlign.Center)
.backgroundColor(page.color)
.borderRadius(12)
})
}
.index(this.currentIndex)
.autoPlay(this.autoPlayEnabled)
.interval(this.autoplayInterval)
.loop(this.loopEnabled)
.duration(400)
.indicator(this.useDigitIndicator ?
new DigitIndicator().fontColor('#FFFFFFAA').selectedFontColor('#FFFFFF') :
new DotIndicator().color('#FFFFFF44').selectedColor('#FFFFFF'))
.displayArrow(this.showArrows ? { showBackground: true, backgroundSize: 28 } : false)
.onChange((index: number) => { this.currentIndex = index; })
关键细节:
- 每个页面是 Colum 容器,宽高固定(100% x 320vp),子元素居中排列
- 最后一页条件渲染"立即开始体验"按钮——这是引导页的标准模式
indicator()通过this.useDigitIndicator@State 在三元表达式中切换 DotIndicator/DigitIndicatordisplayArrow同样通过 @State 控制显隐interval()的值通过控制面板的加减按钮动态调节
3.4 控制面板
控制面板使用 Toggle 开关 + 加减按钮来控制 Swiper 的各种参数:
// 自动播放开关
Row() {
Text('自动播放').fontSize(14).fontColor('#1a1a2e').layoutWeight(1)
Toggle({ type: ToggleType.Switch, isOn: this.autoPlayEnabled })
.selectedColor('#1677FF')
.onChange((value: boolean) => { this.autoPlayEnabled = value; })
}
// 轮播间隔调节
Row() {
Button('-').onClick(() => {
if (this.autoplayInterval > 1000) { this.autoplayInterval -= 500; }
})
Text(`${this.autoplayInterval / 1000}s`)
Button('+').onClick(() => {
if (this.autoplayInterval < 8000) { this.autoplayInterval += 500; }
})
}
间隔调节限制在 1000ms ~ 8000ms 之间,每次增减 500ms,对应 1s ~ 8s。这种渐变式调节让用户可以直观感受不同间隔对轮播速度的影响。
3.5 页面结构
┌──────────────────────────────────────────┐
│ 📱 App 新功能引导(深色标题栏) │
├──────────────────────────────────────────┤
│ 📘 Swiper 组件说明卡片 │
├──────────────────────────────────────────┤
│ ┌ 预览效果 ────────────────────────┐ │
│ │ │ │
│ │ 🏠 │ │
│ │ 智能家居 │ │
│ │ 一键掌控全屋设备... │ │
│ │ │ │
│ │ ● ● ● ● (指示器) │ │
│ │ ← → (可选箭头) │ │
│ └───────────────────────────────────┘ │
├──────────────────────────────────────────┤
│ ┌ 控制面板 ────────────────────────┐ │
│ │ 自动播放 [Toggle ON] │ │
│ │ 无限循环 [Toggle ON] │ │
│ │ 数字指示器 [Toggle OFF] │ │
│ │ 导航箭头 [Toggle OFF] │ │
│ │ 轮播间隔 [-] 3s [+] │ │
│ └───────────────────────────────────┘ │
├──────────────────────────────────────────┤
│ ┌ 当前状态 ────────────────────────┐ │
│ │ 当前页 1 / 4 │ │
│ │ 自动播放 开启中 │ │
│ │ 循环模式 无限循环 │ │
│ │ 指示器 圆点 │ │
│ │ 轮播间隔 3000ms │ │
│ └───────────────────────────────────┘ │
└──────────────────────────────────────────┘

四、Swiper 组件的最佳实践
4.1 index 与 onChange 的双向同步
Swiper 的 index() 和 onChange() 需要配合使用来保持状态与 UI 同步:
@State currentIndex: number = 0;
Swiper()
.index(this.currentIndex)
.onChange((index: number) => { this.currentIndex = index; })
index() 告诉 Swiper “当前应该显示哪一页”——当 @State 变量变化时,Swiper 自动滚动到对应页面。onChange() 在用户手动滑动后更新 @State——这样不管是通过代码切换还是手势滑动,两者始终保持一致。
常见的 bug 是只设了 index() 而忘了 onChange()——此时手动滑动后 @State 不更新,其他依赖该状态的 UI(如"当前页 1/4"的文字)不会刷新。
4.2 在 ForEach 中渲染子页面
Swiper 的每个子组件是一个"页面"。当页面数量动态变化时,ForEach 是最佳选择:
Swiper() {
ForEach(this.pages, (page: PageItem, idx: number) => {
Column() { /* 页面内容 */ }
})
}
注意事项:
ForEach的 key 函数默认使用索引(idx),如果页面数据会增删,建议提供稳定的 key(如 page.id)- 在 ForEach 内部可以通过
idx === this.pages.length - 1判断是否为最后一页——引导页的"进入应用"按钮通常只出现在最后一页 - 每个子页面的尺寸由 Swiper 自动计算(默认填满 Swiper 的宽高),也可以通过设置子组件的宽高来调整
4.3 控制自动播放的行为
自动播放涉及三个参数的配合:autoPlay、interval 和 loop。
Swiper()
.autoPlay(true) // 开启自动播放
.interval(3000) // 3 秒切换一次
.loop(true) // 播放到最后一页后回到第一页
用户手动触摸 Swiper 时,自动播放会自动暂停(避免用户还没看完就被自动切走)。手势结束后,自动播放从 interval 时间开始重新计时——这是 Swiper 的默认行为,无需额外配置。如果需要在手势结束后立即恢复(不等 interval),可以监听 onGestureSwipe 事件手动处理。
4.4 圆点 vs 数字指示器的选择
| 场景 | 推荐指示器 | 原因 |
|---|---|---|
| App 引导页(3~5 页) | DotIndicator | 视觉简洁,符合用户习惯 |
| Banner 轮播(3~8 张) | DotIndicator | 占用空间小,不干扰内容 |
| 教程步骤(>10 步) | DigitIndicator | 圆点太密集时,数字更清晰 |
| 相册浏览 | false(隐藏) | 相册通常不需要指示器 |
DotIndicator 通过 itemWidth/selectedItemWidth 可以实现"选中项长条化"效果——这是 iOS 和 Android 引导页的通用设计。DigitIndicator 通常配合 fontSize 和 fontColor 调整显示效果。
4.5 loop 与边界体验
loop(true) 在极端场景下需要注意:
- 当内容只有 2 页时,loop 会让两个页面之间无限来回——适合,但暗示内容较少
- 当内容只有 1 页时,Swiper 无法滑动,loop 没有实际意义——建议在只有 1 页时直接隐藏 Swiper 或用 Column 替代
- 用户快速连续滑动时,loop 模式的边界过渡可能出现短暂的视觉跳动——增大
cachedCount可缓解
Swiper()
.loop(true)
.cachedCount(3) // 预缓存 3 页,减少边界跳动的闪烁
4.6 Swiper 的性能考量
Swiper 默认只渲染当前页和相邻的一页。当页面内容较重(如包含大图、视频)时,可以通过 cachedCount 调整:
cachedCount(2):缓存当前页前后各 2 页(共 5 页在内存中)- 缓存更多页面能减少滑动时的白屏,但增加内存占用
- 对于轻量内容(纯文本 + 图标),默认值足够
五、完整代码结构
SwiperPage (~280 行)
├── 接口
│ └── PageItem — 引导页数据接口
├── 状态变量
│ ├── @State currentIndex — 当前页索引
│ ├── @State autoPlayEnabled — 自动播放开关
│ ├── @State useDigitIndicator — 数字/圆点指示器
│ ├── @State loopEnabled — 循环模式开关
│ ├── @State showArrows — 导航箭头开关
│ └── @State autoplayInterval — 轮播间隔
├── 实例
│ ├── swiperController: SwiperController
│ └── pages: PageItem[] — 4 页引导数据
├── 视图
│ ├── 标题栏 — 📱 App 新功能引导
│ ├── 说明卡片 — Swiper 组件介绍
│ ├── Swiper 预览区(320vp 高度)
│ ├── 控制面板(4 个 Toggle + 间隔调节按钮)
│ └── 当前状态信息卡片
└── @Builder
└── infoRow() — 状态信息行
六、总结
本文通过一个 App 新功能引导页 Demo 深入讲解了 HarmonyOS 中的 Swiper 轮播滑动组件。Swiper 将滑动切换、自动播放、循环模式、指示器和导航箭头封装为一个完整的声明式组件,开发者只需配置参数即可获得媲美原生体验的轮播效果。
核心要点回顾:
-
Swiper 是"轮播滑动"的完整封装:滑动切换 + 自动播放 + 指示器 + 导航箭头 + 动画控制——所有这些在一个组件内完成。相比手动组合 Scroll + 手势 + 定时器 + 指示器 UI,Swiper 的代码量减少了 80% 以上。
-
autoPlay/interval/loop 三者配合控制播放行为:
autoPlay控制开关,interval控制速度,loop控制边界行为。用户触摸时自动暂停,手势结束后恢复——由 Swiper 内部自动化处理。 -
两种指示器覆盖不同场景:DotIndicator 适合少量页面(引导页、Banner),通过
selectedItemWidth可实现选中项长条效果。DigitIndicator 适合大量页面(教程步骤),显示简洁的"1/N"格式。 -
index 与 onChange 保持双向同步:
index()设置当前页,onChange()响应页面变化。两者绑定同一个 @State,确保不管通过代码还是手势切换,状态始终一致。 -
SwiperController 提供程序化控制能力:
showNext()/showPrevious()/changeIndex()三个方法覆盖了程序化页面切换的所有需求,配合按钮或定时器可实现更灵活的轮播控制。
Swiper 是 ArkUI 中"滑动浏览多页内容"的标准化方案——从 App 引导页到 Banner 轮播,从教程步骤到图片画廊,都可以用 Swiper 构建。理解 autoPlay/interval/loop 的配合逻辑、掌握 DotIndicator/DigitIndicator 的选择和使用、保持 index 与 onChange 的同步——是高效使用 Swiper 的关键。
七、扩展思考
Swiper 解决了基础的轮播滑动需求,但在实际项目中,轮播还有更多变化:
自定义指示器:DotIndicator 和 DigitIndicator 覆盖了 90% 的场景。如果需要完全自定义的指示器(例如带进度条的指示器、带缩略图的指示器),可以通过 Swiper 的 onChange 回调自己渲染指示器 UI,并使用 indicator(false) 隐藏内置指示器。
自定义切换动画:Swiper 默认使用平移动画(curve 可调整曲线)。如果需要 3D 翻转、缩放、淡入淡出等效果,可以使用 customContentTransition 方法配置自定义动画——这是 API 11+ 的高级特性。
Swiper 与 Tabs 的区别:Swiper 的内容是独立的页面(每个页面独立渲染),Tabs 通常与 TabContent 配合使用。Swiper 更灵活(支持循环、自动播放、任意子组件),Tabs 更适合与 Tab 导航栏绑定的场景。
嵌套滚动处理:当 Swiper 内部包含 Scroll/List 等可滚动组件时,需要通过 nestedScroll 方法配置嵌套滚动行为,避免滑动冲突。
与导航路由的配合:引导页通常是 App 的入口——最后一页的按钮触发路由跳转到主页,同时将"已看过引导"的状态存入本地存储,下次启动直接跳过引导页。
理解 Swiper 的定位——在有限空间内容纳多个可滑动切换的页面的容器组件——是正确使用它的关键。它不是导航组件(那是 Navigation/Tabs 的职责),不是列表组件(那是 List/Grid 的职责),而正是这种专注让它成为了引导页和 Banner 轮播的最佳选择。
通过本文的 Demo——App 新功能引导页,你将 4 个引导页面组织在 Swiper 中,体验了从自动播放、循环模式、指示器切换、箭头导航到间隔调节的完整配置流程。这个页面可以作为任何 App 引导页的起点模板。
更多推荐




所有评论(0)