一、引言

滑动切换是移动端中最自然的交互方式之一。App 的引导页逐屏滑动展示新功能,电商首页的 Banner 自动轮播推广商品,相册中左右滑动浏览照片——这些场景背后都有一个共同的组件需求:在有限的屏幕空间内,让多个内容页面可以水平或垂直滑动切换。

传统实现轮播需要组合 Scroll 容器 + 手势检测 + 自动播放定时器 + 指示器 UI + 动画控制,代码量大且细节繁琐:惯性滚动的距离计算、边界回弹效果、自动播放和手动滑动的冲突处理、循环模式的索引映射——每一项单独看都不难,但合在一起就是一个不小的工程。

HarmonyOS 提供了 Swiper 组件——一个功能完备的轮播滑动容器。开发者只需将子组件放入 Swiper,剩下的滑动切换、自动播放、循环模式、指示器、导航箭头等全部由 Swiper 内置实现。它支持圆点指示器(DotIndicator)和数字指示器(DigitIndicator),支持横向/纵向两个方向,支持自定义动画曲线、时长和切换效果。

本文通过一个 App 新功能引导页 Demo 深入讲解 Swiper 组件的核心用法:如何创建多个滑动页面?如何配置自动播放和循环?如何使用圆点和数字指示器?如何显示导航箭头?如何通过 SwiperController 控制页面跳转?

阅读完本文,你将能够:

  • 使用 Swiper 组件创建引导页、Banner 轮播等滑动切换界面
  • 使用 autoPlay/interval/loop 控制自动播放行为
  • 使用 DotIndicatorDigitIndicator 定制指示器样式
  • 使用 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; })

使用模式的关键点:

  1. index() 绑定 @State 变量,在 onChange 中更新,保持两者同步
  2. autoPlayinterval 配合使用,只设 autoPlay 不设 interval 时为默认 3 秒
  3. loop(true) 允许从最后一页无缝切换到第一页
  4. 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 首次启动时展示新功能介绍的经典交互:

  1. 4 个引导页面:每个页面有不同的 emoji 图标、标题、描述和背景色,最后一页有"立即开始体验"按钮
  2. 自动播放:默认每 3 秒自动切换到下一页
  3. 控制面板:5 个可配置项——自动播放开关、无限循环开关、圆点/数字指示器切换、导航箭头开关、轮播间隔调节(1s ~ 8s,每 500ms 一档)
  4. 状态展示:实时显示当前页、自动播放状态、循环模式、指示器类型和轮播间隔

所有 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/DigitIndicator
  • displayArrow 同样通过 @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 控制自动播放的行为

自动播放涉及三个参数的配合:autoPlayintervalloop

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 通常配合 fontSizefontColor 调整显示效果。

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 将滑动切换、自动播放、循环模式、指示器和导航箭头封装为一个完整的声明式组件,开发者只需配置参数即可获得媲美原生体验的轮播效果。

核心要点回顾:

  1. Swiper 是"轮播滑动"的完整封装:滑动切换 + 自动播放 + 指示器 + 导航箭头 + 动画控制——所有这些在一个组件内完成。相比手动组合 Scroll + 手势 + 定时器 + 指示器 UI,Swiper 的代码量减少了 80% 以上。

  2. autoPlay/interval/loop 三者配合控制播放行为autoPlay 控制开关,interval 控制速度,loop 控制边界行为。用户触摸时自动暂停,手势结束后恢复——由 Swiper 内部自动化处理。

  3. 两种指示器覆盖不同场景:DotIndicator 适合少量页面(引导页、Banner),通过 selectedItemWidth 可实现选中项长条效果。DigitIndicator 适合大量页面(教程步骤),显示简洁的"1/N"格式。

  4. index 与 onChange 保持双向同步index() 设置当前页,onChange() 响应页面变化。两者绑定同一个 @State,确保不管通过代码还是手势切换,状态始终一致。

  5. SwiperController 提供程序化控制能力showNext()/showPrevious()/changeIndex() 三个方法覆盖了程序化页面切换的所有需求,配合按钮或定时器可实现更灵活的轮播控制。

Swiper 是 ArkUI 中"滑动浏览多页内容"的标准化方案——从 App 引导页到 Banner 轮播,从教程步骤到图片画廊,都可以用 Swiper 构建。理解 autoPlay/interval/loop 的配合逻辑、掌握 DotIndicator/DigitIndicator 的选择和使用、保持 index 与 onChange 的同步——是高效使用 Swiper 的关键。

七、扩展思考

Swiper 解决了基础的轮播滑动需求,但在实际项目中,轮播还有更多变化:

自定义指示器DotIndicatorDigitIndicator 覆盖了 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 引导页的起点模板。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐