HarmonyOS 6.1 实战:Swiper 轮播图与页面滑动详解
前言
Swiper是 ArkUI 中专为轮播图、引导页、图片画廊等场景设计的滑动容器。它内置指示器、自动播放、循环滑动等能力,配合SwiperController可以实现编程式翻页。本文通过完整示例演示 Swiper 的核心特性。
运行效果
初始状态(Banner 轮播)
滑动至第二页
核心 API 一览
| API | 说明 |
|---|---|
Swiper(controller?) | 轮播容器,子组件逐页显示 |
.loop(bool) | 是否循环播放,默认 true |
.autoPlay(bool) | 是否自动播放 |
.interval(ms) | 自动播放间隔(毫秒) |
.indicator(bool/DotIndicator/DigitIndicator) | 指示器样式 |
.index(n) | 当前页索引 |
.duration(ms) | 翻页动画时长 |
.curve(Curve) | 翻页动画曲线 |
.onChange(idx) | 页面切换回调 |
.itemSpace(n) | 相邻页的间距(用于“卡片流“效果) |
controller.showNext() | 编程式切换到下一页 |
controller.showPrevious() | 编程式切换到上一页 |
完整示例代码
interface BannerItem { title: string subtitle: string bg: string icon: string } interface CardItem { name: string desc: string color: string } @Entry @Component struct Index { @State currentBanner: number = 0 @State currentCard: number = 0 @State autoPlay: boolean = true private bannerController: SwiperController = new SwiperController() private cardController: SwiperController = new SwiperController() private banners: BannerItem[] = [ { title: 'HarmonyOS 6.1', subtitle: '全新 ArkUI 组件体系正式上线', bg: '#0044cc', icon: '🚀' }, { title: '折叠屏适配', subtitle: '一套代码,完美适配多种屏幕形态', bg: '#cc3300', icon: '📱' }, { title: '性能优化', subtitle: 'Swiper 流畅帧率提升 40%', bg: '#006633', icon: '⚡' }, { title: '开发者工具', subtitle: 'DevEco Studio 全面升级', bg: '#6600aa', icon: '🛠' }, ] private cards: CardItem[] = [ { name: '组件库', desc: '200+ 内置 UI 组件', color: '#e8f0ff' }, { name: '动画引擎', desc: '60fps 流畅动效', color: '#fff0e8' }, { name: '跨平台', desc: '一次开发多端运行', color: '#e8fff0' }, { name: '状态管理', desc: 'MVVM 响应式架构', color: '#f8e8ff' }, ] build() { Scroll() { Column({ space: 20 }) { Text('Swiper 轮播 & 页面滑动') .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#1a1a1a') .width('100%') .padding({ left: 16, top: 16 }) // ── 1. 全屏 Banner 轮播 ── Column({ space: 10 }) { Text('① 全屏 Banner(自动轮播)') .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor('#333') .padding({ left: 16 }) Swiper(this.bannerController) { ForEach(this.banners, (item: BannerItem) => { Column({ space: 8 }) { Text(item.icon).fontSize(48) Text(item.title) .fontSize(22) .fontWeight(FontWeight.Bold) .fontColor('#ffffff') Text(item.subtitle) .fontSize(13) .fontColor('rgba(255,255,255,0.85)') .textAlign(TextAlign.Center) .padding({ left: 24, right: 24 }) } .width('100%') .height(180) .backgroundColor(item.bg) .justifyContent(FlexAlign.Center) .borderRadius(12) }) } .loop(true) .autoPlay(this.autoPlay) .interval(2500) .duration(400) .curve(Curve.EaseOut) .indicator( new DotIndicator() .itemWidth(6) .itemHeight(6) .selectedItemWidth(20) .selectedItemHeight(6) .color('#88bbff') .selectedColor('#ffffff') ) .onChange((idx: number) => { this.currentBanner = idx }) .margin({ left: 16, right: 16 }) // 控制按钮 Row({ space: 10 }) { Button('上一张') .height(36) .fontSize(13) .backgroundColor('#0066ff') .onClick(() => { this.bannerController.showPrevious() }) Text('第 ' + (this.currentBanner + 1).toString() + ' / ' + this.banners.length.toString() + ' 张') .fontSize(13) .fontColor('#888') .layoutWeight(1) .textAlign(TextAlign.Center) Button('下一张') .height(36) .fontSize(13) .backgroundColor('#0066ff') .onClick(() => { this.bannerController.showNext() }) Button(this.autoPlay ? '停止' : '自动') .height(36) .fontSize(13) .backgroundColor(this.autoPlay ? '#ff6600' : '#00aa44') .onClick(() => { this.autoPlay = !this.autoPlay }) } .width('100%') .padding({ left: 16, right: 16 }) } .width('100%') // ── 2. 卡片流(itemSpace + displayCount)── Column({ space: 10 }) { Text('② 卡片流(部分预览相邻页)') .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor('#333') .padding({ left: 16 }) Swiper(this.cardController) { ForEach(this.cards, (card: CardItem) => { Column({ space: 8 }) { Text(card.name) .fontSize(18) .fontWeight(FontWeight.Bold) .fontColor('#333') Text(card.desc) .fontSize(13) .fontColor('#888') } .width('100%') .height(100) .backgroundColor(card.color) .borderRadius(14) .justifyContent(FlexAlign.Center) .border({ width: 1, color: '#e0e0e0' }) }) } .loop(false) .autoPlay(false) .itemSpace(12) .indicator(false) .curve(Curve.Smooth) .onChange((idx: number) => { this.currentCard = idx }) .margin({ left: 32, right: 32 }) Row({ space: 4 }) { ForEach(this.cards, (_c: CardItem, idx: number) => { Column() .width(this.currentCard === idx ? 20 : 6) .height(6) .backgroundColor(this.currentCard === idx ? '#0066ff' : '#d0d0d0') .borderRadius(3) }) } .justifyContent(FlexAlign.Center) } .width('100%') // ── 3. 数字指示器 ── Column({ space: 10 }) { Text('③ 数字指示器(DigitIndicator)') .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor('#333') .padding({ left: 16 }) Swiper() { ForEach(['#ff6b6b', '#feca57', '#48dbfb', '#1dd1a1'], (color: string) => { Column() .width('100%') .height(100) .backgroundColor(color) .borderRadius(12) }) } .loop(true) .autoPlay(true) .interval(1800) .indicator( new DigitIndicator() .fontColor('#ffffff') .selectedFontColor('#ffffff') .digitFont({ size: 14, weight: FontWeight.Bold }) ) .margin({ left: 16, right: 16 }) } .width('100%') Column().height(20) } .width('100%') } .width('100%') .height('100%') .backgroundColor('#ffffff') } }关键知识点
1. 指示器(Indicator)三种形式
// 布尔值:显示/隐藏默认圆点 .indicator(true) .indicator(false) // DotIndicator:自定义圆点样式 .indicator( new DotIndicator() .itemWidth(6).itemHeight(6) .selectedItemWidth(20).selectedItemHeight(6) // 选中时拉长为胶囊 .color('#cccccc') .selectedColor('#0066ff') ) // DigitIndicator:数字形式 "1 / 4" .indicator( new DigitIndicator() .fontColor('#ffffff') .selectedFontColor('#ffffff') )2. 卡片流效果
通过.itemSpace(n)设置相邻卡片间距,并给 Swiper 左右各留 margin(大于间距),就能看到相邻卡片的“预览“效果:
Swiper() .itemSpace(12) // 相邻卡片间距 .indicator(false) // 通常卡片流不需要指示器 .margin({ left: 32, right: 32 }) // 留出边距,让相邻卡片露出3. SwiperController 编程式控制
private controller: SwiperController = new SwiperController() Swiper(this.controller) { ... } // 翻页 this.controller.showNext() this.controller.showPrevious()4. 动画曲线(Curve)
| 值 | 效果 | 适合场景 |
|---|---|---|
Curve.Linear | 匀速 | 少用 |
Curve.EaseOut | 减速结束 | Banner 轮播 |
Curve.Smooth | 平滑过渡 | 卡片翻页 |
Curve.FastOutSlowIn | Material 风格 | 通用 |
5. onChange 与 index 的关系
.onChange(idx)在每次翻页完成时触发,idx是目标页的索引(从 0 开始)。若需要初始化时指定展示页,用.index(n)属性:
Swiper() .index(2) // 从第 3 页(下标 2)开始显示 .onChange((idx: number) => { this.currentPage = idx })小结
Swiper专为页面级滑动和轮播设计,内置指示器、自动播放、循环滑动DotIndicator支持选中态拉伸为胶囊形,视觉上更优雅SwiperController提供showNext/showPrevious用于外部按钮控制itemSpace + margin组合实现卡片流“露出相邻页“的效果- 注意区分
loop(逻辑循环)和autoPlay + interval(自动翻页)是独立配置项
