【学习目标】

  • 掌握Swiper轮播组件的核心架构、布局约束规则,理解轮播容器的核心适配逻辑
  • 吃透Swiper八大核心能力:循环播放、自动轮播、轮播方向控制、导航点自定义、箭头样式配置、页面切换控制、多子项同屏显示、自定义切换动画
  • 掌握SwiperController控制器的完整用法,实现手动翻页、指定页跳转、动画模式自定义等精细化页面控制
  • 掌握轮播组件的性能优化方案,理解预加载、懒加载适配、内存管控的最佳实践
  • 独立实现首页广告Banner、商品卡片轮播、Tabs页签联动三大高频业务场景,完成可直接落地的商业级轮播组件标准化开发
  • 攻克轮播开发高频踩坑点,解决数据增删视图跳动、动画不流畅、样式适配等核心问题

一、工程目录结构

SwiperDemo/
├── entry/
│   └── src/
│       └── main/
│           ├── ets/
│           │   ├── entryability/
│           │   │   └── EntryAbility.ets       // 应用入口
│           │   ├── pages/
│           │   │   ├── Index.ets              // 导航首页
│           │   │   ├── SwiperBaseDemo.ets     // 基础全量示例
│           │   │   └── HomeBannerDemo.ets     // 首页Banner商业实战
│           │   ├── model/
│           │   │   └── SwiperItemData.ets       轮播数据模型
│           │   ├── components/
│           │   │   └── BannerCard.ets         // 轮播Banner卡片组件
│           │   └── datasource/
│           │       └── SwiperDataSource.ets   // 轮播Banner数据源
│           ├── resources/                     
│           └── module.json5                    // 模块配置文件
└── build-profile.json5                          // 工程配置文件

导航首页代码(Index.ets)

import router from '@ohos.router';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      Text('Swiper 轮播布局教学示例')
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 });

      Button('1. 轮播基础全量示例', { type: ButtonType.Capsule })
        .width('85%')
        .height(55)
        .fontSize(17)
        .onClick(() => {
          router.pushUrl({ url: 'pages/SwiperBaseDemo' });
        });

      Button('2. 首页Banner商业实战', { type: ButtonType.Capsule })
        .width('85%')
        .height(55)
        .fontSize(17)
        .onClick(() => {
          router.pushUrl({ url: 'pages/HomeBannerDemo' });
        });
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

二、轮播布局核心基础

2.1 核心概念与架构

轮播布局是App首页、活动运营页最主流的滑动切换容器方案,核心由Swiper轮播容器组件承载,可对多个子组件进行循环滑动切换展示。

它的核心特点是支持手势滑动、自动轮播、循环切换、自定义指示器、精细化动画控制,完美适配广告Banner、卡片滑动、全屏内容切换等场景。针对复杂页面场景,Swiper还提供预加载机制,利用主线程空闲时间提前构建组件,优化滑动体验。

核心铁则:Swiper作为容器组件,可嵌套任意布局组件作为轮播子项,无需专属子组件配合,子项尺寸默认跟随Swiper容器规则自适应。

2.2 Swiper核心布局与约束规则

  1. 若Swiper设置了自身尺寸属性,则轮播过程中始终以该尺寸生效,子项尺寸跟随容器适配
  2. 若未设置自身尺寸,分两种情况:
    • 设置了prevMargin/nextMargin属性:Swiper尺寸跟随其父组件
    • 未设置prevMargin/nextMargin属性:Swiper尺寸自动根据子组件大小设置
  3. 仅设置loop=true时,可实现首尾无限循环切换,滑动到第一页/最后一页时可继续无缝切换
  4. 配合LazyForEach使用时,可通过maintainVisibleContentPosition属性保证数据增删时,当前可见内容位置不变,避免视图跳动

三、基础用法

本示例将覆盖Swiper轮播的所有核心基础知识点,包括循环播放、自动轮播、轮播方向、导航点自定义、箭头配置、控制器控制、多子项同屏、懒加载适配等。

3.1 轮播项数据模型

 export interface SwiperItemData {
  id: string;
  title: string;
  imageUrl: string;
}

3.2 数据源

import { SwiperItemData } from '../model/SwiperItemData';

// 懒加载数据源
export class SwiperDataSource implements IDataSource {
  private dataList: SwiperItemData[] = [];
  private listener: DataChangeListener | undefined;

  constructor(initList: SwiperItemData[]) {
    this.dataList = initList;
  }

  // 头部新增数据
  addHeaderData(data: SwiperItemData): void {
    this.dataList.unshift(data);
    this.listener?.onDataAdd(0);
  }

  // 删除头部数据
  deleteHeaderData(): void {
    if (this.dataList.length > 0) {
      this.dataList.shift();
      this.listener?.onDataMove(0, 1);
    }
  }

  totalCount(): number {
    return this.dataList.length;
  }

  getData(index: number): SwiperItemData {
    return this.dataList[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener;
  }

  unregisterDataChangeListener(): void {
    this.listener = undefined;
  }
}

3.3 轮播基础示例

import { SwiperItemData } from '../model/SwiperItemData';
import { LengthMetrics } from '@kit.ArkUI';
import { SwiperDataSource } from '../datasource/SwiperDataSource';
import { util } from '@kit.ArkTS';

@Entry
@Component
struct SwiperBaseDemo {
    // 轮播基础数据
    private swiperDataList: SwiperItemData[] = [ 
      { id: util.generateRandomUUID(true), title: '轮播页1', imageUrl:`https://picsum.photos/1024/960`,
      },
      { id: util.generateRandomUUID(true), title: '轮播页2', imageUrl:`https://picsum.photos/1024/960`,
      },
      { id: util.generateRandomUUID(true), title: '轮播页3', imageUrl:`https://picsum.photos/1024/960`,
      },
      { id: util.generateRandomUUID(true), title: '轮播页4', imageUrl:`https://picsum.photos/1024/960`,
      },
      { id: util.generateRandomUUID(true), title: '轮播页5', imageUrl:`https://picsum.photos/1024/960`,
      }
    ];
    
    // 数据源与控制器
    private dataSource: SwiperDataSource = new SwiperDataSource(this.swiperDataList);
    private swiperController: SwiperController = new SwiperController();

    // 状态变量
    @State currentIndex: number = 0;
    @State isAutoPlay: boolean = true;
    @State isLoop: boolean = true;
    @State isVertical: boolean = false;
    @State displayCount: number = 1;

    build() {
      Column({ space: 20 }) {
        // 顶部标题
        Text('Swiper 轮播基础全量示例')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 10 })

        // 核心轮播容器
        Swiper(this.swiperController) {
          LazyForEach(this.dataSource, (item: SwiperItemData) => {
            // 轮播子项内容
            Stack({alignContent:Alignment.TopEnd}){
              Image(item.imageUrl)
              Text(item.title)
                .fontSize(30)
                .padding(10)
                .fontColor(Color.White)
                .fontWeight(FontWeight.Bold)
            }
            .width('100%')
            .height('100%')
            .borderRadius(12)
          }, (item: SwiperItemData) => item.id)
        }
        // 核心属性1:循环播放控制
        .loop(this.isLoop)
        // 核心属性2:自动轮播控制
        .autoPlay(this.isAutoPlay)
        // 核心属性3:自动轮播间隔,单位毫秒
        .interval(3000)
        // 核心属性4:轮播方向控制
        .vertical(this.isVertical)
        // 核心属性5:当前显示索引
        .index(this.currentIndex)
        // 核心属性6:单页显示子项数量
        .displayCount(this.displayCount)
        // 核心属性7:指示器自定义
        .indicator(
          Indicator.dot()
            .bottom(LengthMetrics.vp(10), true) // 忽略组件尺寸,贴底显示
            .space(LengthMetrics.vp(6)) // 导航点间距
            .itemWidth(8)
            .itemHeight(8)
            .selectedItemWidth(16)
            .selectedItemHeight(8)
            .color('#CCCCCC')
            .selectedColor('#007AFF')
        )
        // 核心属性8:导航箭头显示与自定义
        .displayArrow({
          showBackground: true,
          isSidebarMiddle: true,
          backgroundSize: 24,
          backgroundColor: Color.White,
          arrowSize: 18,
          arrowColor: Color.Blue
        }, false)
        // 核心属性9:懒加载数据增删时,保持可见内容位置不变
        .maintainVisibleContentPosition(true)
        // 宽高设置
        .width('100%')
        .height(250)
        .backgroundColor('#F5F5F5')
        .borderRadius(12)
        // 核心事件1:页面切换完成后触发,返回当前索引
        .onSelected((index: number) => {
          console.info(`轮播切换到第${index}`);
          this.currentIndex = index;
        })
        // 核心事件2:索引变化时实时触发
        .onChange((index: number) => {
          this.currentIndex = index;
        })

        // 控制按钮区
        Column({ space: 12 }) {
          // 基础控制按钮
          Row({ space: 12 }) {
            Button('上一页')
              .onClick(() => {
                this.swiperController.showPrevious();
              })
            Button('下一页')
              .onClick(() => {
                this.swiperController.showNext();
              })
          }

          // 跳转指定页
          Row({ space: 12 }) {
            Text(`当前页:${this.currentIndex}`)
              .fontSize(14)
            Button('跳转到第3页')
              .onClick(() => {
                this.swiperController.changeIndex(2, true);
              })
          }

          // 开关控制区
          Row({ space: 12 }) {
            Text(`自动轮播:${this.isAutoPlay}`)
              .fontSize(14)
            Toggle({ type: ToggleType.Switch, isOn: this.isAutoPlay })
              .onChange((isOn: boolean) => {
                this.isAutoPlay = isOn;
              })
          }

          Row({ space: 12 }) {
            Text(`循环播放:${this.isLoop}`)
              .fontSize(14)
            Toggle({ type: ToggleType.Switch, isOn: this.isLoop })
              .onChange((isOn: boolean) => {
                this.isLoop = isOn;
              })
          }

          Row({ space: 12 }) {
            Text(`垂直轮播:${this.isVertical}`)
              .fontSize(14)
            Toggle({ type: ToggleType.Switch, isOn: this.isVertical })
              .onChange((isOn: boolean) => {
                this.isVertical = isOn;
              })
          }

          // 显示数量控制
          Row({ space: 12 }) {
            Text(`单页显示数量:${this.displayCount}`)
              .fontSize(14)
            Button('1个')
              .onClick(() => {
                this.displayCount = 1;
              })
            Button('2个')
              .onClick(() => {
                this.displayCount = 2;
              })
          }

          // 数据增删控制
          Row({ space: 12 }) {
            Button('头部新增数据')
              .onClick(() => {
                const newData: SwiperItemData = {
                  id: util.generateRandomUUID(true),
                  title: '新增轮播页',
                  imageUrl: `https://picsum.photos/375/${Math.floor(Math.random() * 300) + 100}`
  };

  this.dataSource.addHeaderData(newData);
  })
  Button('头部删除数据')
    .onClick(() => {
      this.dataSource.deleteHeaderData();
    })
  }
  }
  .width('100%')
    .padding(12)
    .backgroundColor($r('sys.color.comp_background_list_card'))
    .borderRadius(12)
  }
  .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

3.2 知识点讲解

  1. 基础核心属性:通过loop/autoPlay/interval/vertical/index/displayCount六大属性,覆盖轮播的基础播放、方向、显示数量等核心控制能力
  2. 指示器自定义:通过indicator属性实现导航点的位置、间距、尺寸、颜色全量定制,同时通过bottom(bottom, ignoreSize)参数解决导航点底部间距问题
  3. 导航箭头配置:通过displayArrow属性实现箭头的显示、样式、背景自定义,满足不同设计风格的需求
  4. 控制器精细化控制:通过SwiperController实现上一页、下一页、跳转到指定页的手动控制,覆盖业务中按钮控制轮播的高频需求
  5. 懒加载与数据适配:实现IDataSource接口,配合LazyForEach实现数据懒加载,通过maintainVisibleContentPosition属性解决数据增删时的视图跳动问题
  6. 生命周期事件:通过onSelected/onChange事件监听轮播页面切换,实现索引同步、埋点统计、联动UI更新等业务逻辑
  7. 预加载优化:Swiper原生预加载机制,利用主线程空闲时间提前构建组件,优化滑动体验

四、商业级实战案例:App首页Banner与Tabs联动开发

在掌握了基础用法之后,我们通过完整的App首页Banner实战,落地轮播的高级特性、自定义动画、与Tabs联动等商业级需求,实现可直接复用的首页轮播模块。

4.1 模拟数据准备(utils/SwiperMockData.ets)

// Banner数据模型
@Observed
export class BannerItem {
  // 类成员属性
  id: string;
  title: string;
  coverUrl: string;
  linkUrl: string;

  // 构造函数:创建对象时自动赋值
  constructor(id: string, title: string, coverUrl: string, linkUrl: string) {
    this.id = id;
    this.title = title;
    this.coverUrl = coverUrl;
    this.linkUrl = linkUrl;
  }

  // 静态方法:生成模拟数据
  static generateBannerData(): BannerItem[] {
    return [
      new BannerItem(
        '1',
        '春日新品首发',
        'https://picsum.photos/1920/1080?random=1',
        'pages/ActivitySpring'
      ),
      new BannerItem(
        '2',
        '限时优惠活动',
        'https://picsum.photos/1920/1080?random=2',
        'pages/ActivitySale'
      ),
      new BannerItem(
        '3',
        '会员专属福利',
        'https://picsum.photos/1920/1080?random=3',
        'pages/MemberCenter'
      ),
      new BannerItem(
        '4',
        '新品上市',
        'https://picsum.photos/1920/1080?random=4',
        'pages/NewProduct'
      ),
      new BannerItem(
        '5',
        '品牌日狂欢',
        'https://picsum.photos/1920/1080?random=5',
        'pages/BrandDay'
      )
    ];
  }
}
export class HomeTabItem {
  id: string;
  name: string;

  // 构造函数
  constructor(id: string, name: string) {
    this.id = id;
    this.name = name;
  }

  // 静态方法生成数据
  static generateHomeTabData(): HomeTabItem[] {
    return [
      new HomeTabItem('1', '推荐'),
      new HomeTabItem('2', '新品'),
      new HomeTabItem('3', '热卖'),
      new HomeTabItem('4', '优惠'),
      new HomeTabItem('5', '会员')
    ];
  }
}

4.2 可复用Banner卡片组件(components/BannerCard.ets)

import { BannerItem } from "../model/BannerItem";

@Reusable
@Component
export struct BannerCard {
  @ObjectLink banner: BannerItem;

  aboutToReuse(params: Record<string, BannerItem>) {

  }

  build() {
    Stack() {

      Image(this.banner.coverUrl)
        .width('100%')
        .height('100%')
      // Banner标题
      Text(this.banner.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
        .position({ left: 20, bottom: 30 })
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
    }
    .width('100%')
    .height('100%')
    .onClick(() => {
      console.info(`点击Banner:${this.banner.title},跳转链接:${this.banner.linkUrl}`);
    })
  }
}

4.3 首页Banner与Tabs联动示例代码(pages/HomeBannerDemo.ets)

import { BannerCard } from '../component/BannerCard';
import { BannerItem } from '../model/BannerItem';
import { HomeTabItem } from '../model/HomeTabItem';
import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
struct HomeBannerDemo {
  // 数据初始化
  @State bannerList: BannerItem[] = BannerItem.generateBannerData();
  @State tabList: HomeTabItem[] = HomeTabItem.generateHomeTabData();
  // 状态变量
  @State currentIndex: number = 0;
  @State tabCurrentIndex: number = 0;

  // 控制器
  private swiperController: SwiperController = new SwiperController();
  private tabsController: TabsController = new TabsController();

  build() {
    Column() {
      // 顶部状态栏
      Row() {
        Text('首页')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
        Blank()
        Image($r('app.media.icon_search'))
          .width(24)
          .height(24)
          .fillColor('#333333')
      }
      .width('100%')
      .height(50)
      .padding({ left: 16, right: 16 })
      .backgroundColor(Color.White)

      // 核心Banner轮播区
      Swiper(this.swiperController) {
        ForEach(this.bannerList, (item: BannerItem) => {
          BannerCard({ banner: item })
            .width('100%')
            .height('100%')
        }, (item: BannerItem) => item.id)
      }
      .loop(true)
      .autoPlay(true)
      .interval(4000)
      .indicator(
        Indicator.dot()
          .bottom(10, true)
          .space(LengthMetrics.vp(6))
          .itemWidth(6)
          .itemHeight(6)
          .selectedItemWidth(12)
          .selectedItemHeight(6)
          .color('#FFFFFF80')
          .selectedColor(Color.White)
      )
      .width('100%')
      .height(200)
      .onSelected((index: number) => {
        this.currentIndex = index;
      })

      // 分类Tab栏
      Tabs({ barPosition: BarPosition.Start, index:this.currentIndex,controller: this.tabsController }) {

        ForEach(this.tabList, (tab: HomeTabItem, index: number) => {

          TabContent() {
            Column() {
              Text(`${tab.name}内容区`)
                .fontSize(16)
                .fontColor('#666666')
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#F5F5F5')
          }
          .tabBar(this.TabBarBuilder(tab.name,index))
        }, (tab: HomeTabItem) => tab.id)
      }
      .onTabBarClick((index: number) => {
        this.tabCurrentIndex = index;
        // Tab点击同步切换Swiper
        this.swiperController.changeIndex(index % this.bannerList.length, true);
      })
      .barMode(BarMode.Scrollable)
      .onChange((index:number)=>{
        this.tabCurrentIndex = index
      })
      .animationDuration(300)
      .backgroundColor(Color.White)
      .barHeight(50)
      .barMode(BarMode.Fixed)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  // TabBar自定义构建器
  @Builder
  TabBarBuilder(name: string, index: number) {
    Column() {
      Text(name)
        .fontSize(index === this.tabCurrentIndex ? 16 : 14)
        .fontColor(index === this.tabCurrentIndex ? '#007AFF' : '#666666')
        .fontWeight(index === this.tabCurrentIndex ? FontWeight.Bold : FontWeight.Medium)
    }
    .padding({ left: 16, right: 16 })
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

五、最佳实践与高频避坑指南

5.1 开发最佳实践

  1. 循环播放规范:广告Banner、卡片轮播等场景默认开启loop=true,引导页、步骤页等有明确首尾边界的场景关闭循环播放
  2. 自动轮播规范:首页Banner推荐开启自动轮播,间隔设置为3000-5000毫秒,避免切换过快影响用户浏览;用户手动滑动后暂停自动轮播,滑动结束后恢复
  3. 指示器规范:轮播项超过3个时必须显示指示器,指示器位置推荐底部居中,选中态与未选中态要有明确视觉区分
  4. 性能规范:轮播项超过5个时,必须使用LazyForEach懒加载,配合@Reusable装饰器实现组件复用,降低内存占用
  5. 数据适配规范:动态增删轮播数据时,必须开启maintainVisibleContentPosition=true,避免数据变化导致视图跳动,影响用户体验

六、内容总结

  1. 核心架构:轮播布局采用Swiper容器组件为核心,可嵌套任意布局组件作为子项,无需专属子组件配合,天然支持手势滑动与循环切换
  2. 核心配置:通过loop/autoPlay/interval控制播放规则,通过indicator/displayArrow自定义指示器与箭头,通过SwiperController实现精细化控制
  3. 核心能力:循环播放、自动轮播、横竖屏方向切换、多子项同屏显示、自定义切换动画、与Tabs双向联动、懒加载数据适配
  4. 核心场景:App首页广告Banner、商品卡片轮播、引导页、全屏短视频切换、活动页图片轮播等商业级高频场景
  5. 性能优化:懒加载数据适配、组件复用、预加载机制、maintainVisibleContentPosition视图位置保持,解决轮播滑动卡顿、视图跳动等核心问题

六、代码仓库

  • 工程名称:SwiperDemo
  • 仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git

七、下节预告

下一节我们将进入触摸手势交互体系的系统学习,从 Tap、Pan、Pinch、Rotation、Swipe 等基础手势,到手势绑定优先级、父子事件分发、组合手势(顺序/并行/互斥)与多层级手势控制,全面掌握鸿蒙交互开发能力。

Logo

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

更多推荐