跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScript大前端

鸿蒙应用开发:使用 Swiper 组件实现复杂轮播图

在鸿蒙应用开发中利用 Swiper 组件实现两种复杂轮播效果的方案。首先,通过图文作品合集场景,展示了结合 LazyForEach 与自定义进度条动画实现自动轮播及手动切换交互的方法,并适配了折叠屏设备的状态变化。其次,通过叠加效果场景,演示了利用 Stack 布局、手势监听(PanGesture)及属性动画(animateTo)创建多层卡片视觉层叠与平滑切换体验的技术细节。核心涉及 Swiper 配置、自定义指示器、折叠屏 API 调用及复杂手势处理逻辑。

霸天发布于 2026/3/25更新于 2026/10/23102 浏览
鸿蒙应用开发:使用 Swiper 组件实现复杂轮播图

Swiper 组件实现复杂轮播图

概述

在各类应用和网站中,轮播图的使用非常广泛,它在信息展示和用户交互方面扮演着重要角色。轮播图不仅能在有限的屏幕区域内展示更多内容,还能有效地将关键的信息传递给用户。在开发应用或网站时,可以通过轮播图优先展示重要内容,次要内容则随后呈现,用户能够自主控制浏览节奏,滑动交互也能为用户带来发现内容的愉悦感,从而提升用户体验。

本文将通过以下两个场景介绍如何使用 Swiper 组件实现不同的轮播效果:

  1. 使用 Swiper 实现图文作品合集:图文作品合集由图片和文字组合而成,通过 Swiper 组件来动态展示图片,实现图片的轮播效果。
  2. 实现轮播图片叠加效果:轮播图的叠加效果(多层轮播图视觉叠加)可以创造独特的视觉层次和交互体验。

使用 Swiper 实现图文作品合集

场景描述

在一些短视频平台上,经常能看到由图片和文字组合而成的作品集。这些作品集通常由多张图片构成,支持自动轮播或手动切换。

  1. 当作品自动播放时,图片会每隔几秒自动切换到下一张,且下方的进度条进度与每张图片的停留时间相匹配。
  2. 当用户主动触发播放操作时,下方进度条会跟着图片的滑动切换而改变成未完成状态或已完成状态。

效果如图所示。

效果示意图

实现原理

图文作品轮播可以通过 Swiper 组件及其指示器的联动效果来实现。由于 Swiper 组件的指示器不可自定义,因此需要分开实现。

图片区域需要使用 Swiper 组件来实现。将图片合集的数据传入 Swiper 组件后,需要对 Swiper 组件设置一些属性,来完成图片自动轮播效果。

  1. 通过设置 loop 属性控制是否循环播放,该属性默认值为 true。当 loop 为 true 时,在显示第一页或最后一页时,可以继续往前切换到前一页或者往后切换到后一页。如果 loop 为 false,则在第一页或最后一页时,无法继续向前或者向后切换页面。
  2. 通过设置 autoPlay 属性,控制是否自动轮播子组件。该属性默认值为 false。autoPlay 为 true 时,会自动切换播放子组件。
  3. 通过设置 interval 属性,控制子组件与子组件之间的播放间隔。interval 属性默认值为 3000,单位毫秒。
  4. 通过设置 indicator 属性为 false,来关闭 Swiper 组件自带的导航点指示器样式。

底部导航点(进度条)有三种样式:未完成状态的样式、已完成状态的样式和正在进行进度增长的样式。

  1. 进度条布局:开发者可以使用层叠布局 (Stack),配合 Row 容器来实现进度条的布局。
  2. 图文播放时间与进度条匹配:要实现进度条缓慢增长至完成状态且用时与图片播放时间相匹配的效果,可以给 Row 容器组件添加属性动画 (animation),设置 duration(动画持续时间)与图片播放时间匹配即可。
  3. 进度条状态切换:通过比较当前图片的 currentIndex 与进度条的 index,当 currentIndex 大于 index 时,应将进度条样式设置为已完成状态;反之,则设置为未完成状态。可以通过将进度条的背景颜色设置为 Color.White 或 Color.Grey 来实现这两种状态的切换。

开发步骤

  1. Swiper 组件初始化:
    • 使用 Swiper 组件创建一个轮播图容器,传入 this.swiperController 作为控制器。
  2. 数据加载与展示:
    • 使用 LazyForEach 遍历 this.data 数组,其中每个元素是一个 PhotoData 对象。
    • 对于每个 PhotoData 对象,创建一个 Image 组件,图片资源通过 $r 函数加载,路径为 app.media. 加上 item.id。
  3. 图片尺寸调整:
    • 图片的宽度根据 foldStatus 的值动态调整:如果 foldStatus 为 2,宽度为 100%;否则宽度为 70%。
    • 图片高度固定为 100%。
  4. 轮播图配置:
    • 设置轮播图为循环模式(.loop(true))。
    • 启用自动播放功能(.autoPlay(true)),每 3 秒(.interval(3000))切换一次图片。
    • 禁用指示器显示(.indicator(false)),即不显示当前页面的指示点。
  5. 函数定义与布局初始化:
    • 使用 @Builder 装饰器定义 progressComponent 函数,这是一个构建器函数,用于创建 UI 组件。
    • 使用 Row 组件创建一个水平布局,设置 space 为 5,用于在进度条之间添加间距。
  6. 循环渲染进度条:
    • 使用 ForEach 循环遍历 this.progressData 数组,每个元素为 PhotoData 类型。
    • 对于每个 PhotoData 元素,创建一个 Stack 组件,设置 alignContent 为 Alignment.Start,用于对齐子组件。
  7. 进度条样式与动画:
    • 在 Stack 组件中嵌套一个 Row 组件,设置 zIndex 为 1,确保其在 Stack 中的层级较高。
    • 设置 width 和 height 属性,根据当前索引是否等于数据索引来决定进度条的宽度和高度。
    • 使用 borderRadius 设置圆角效果,并设置背景颜色为白色。
    • 添加动画效果,使用 animation 方法定义动画参数,包括持续时间、曲线类型、迭代次数和播放模式。动画的持续时间和曲线类型根据当前索引是否等于数据索引来决定。
  8. 布局权重设置:
    • 使用 layoutWeight(1) 设置每个 Stack 组件的布局权重为 1,确保它们在水平方向上均匀分布。
  9. 整体布局设置:
    • 设置 Row 组件的 width 为 '100%',height 为 50,确保整个组件的宽度和高度符合预期。

滑动切换图片后,关闭自动轮播与循环轮播。此时,开发者需要给 Swiper 组件添加 onGestureSwipe 事件,来判断页面是否跟手滑动。其中 slide 为布尔值,用来判断页面是否跟手滑动。默认值为 false,当页面跟手滑动时,slide 的值为 true。当进行滑动切换时,autoPlay、loop 属性的取值为 false,即关闭自动轮播与循环播放功能。若想实现滑动图片后仍自动循环轮播,直接去掉 slide 相关代码片段即可。

Swiper(this.swiperController) {
    // ... 
}
// .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
//     this.slide = true;
// })

示意效果如下图所示。

创建进度条自定义组件 progressComponent。代码中,this.progressData 为图片集合的数组,this.currentIndex 为当前播放的图片在图片集合数组中的索引,index 为进度条对应的图片在图片集合数组中的索引。当 this.currentIndex > index 时,表示图片集合数组中索引 0-index 的进度条都是已完成状态。

@Builder progressComponent() {
    Row({ space: 5 }) {
        ForEach(this.progressData, (item: PhotoData, index: number) => {
            Stack({ alignContent: Alignment.Start }) {
                // Use the cascading component to stack progress bars of different styles together
                // ...
                Row().zIndex(1).width(this.currentIndex === index ? '100%' : '0').height(2).borderRadius(2).backgroundColor(Color.White)
                // Add a growth animation to the progress bar
                .animation({ duration: this.currentIndex === index ? this.duration : 0, curve: Curve.Linear, iterations: 1, playMode: PlayMode.Normal })
                // ...
            }.layoutWeight(1)
        }, (item: PhotoData) => JSON.stringify(item))
    }.width('100%').height(50)
}

为 Swiper 组件设置 loop、autoPlay、interval 和 indicator 属性。在手指未滑动的情况下,图片每 3 秒会进行一次切换,并且自动进行轮播。

Swiper(this.swiperController) {
    LazyForEach(this.data, (item: PhotoData, index: number) => {
        Image($r(`app.media.` + item.id)).width(this.foldStatus === 2 ? '100%' : '70%').height('100%')
    }, (item: PhotoData) => JSON.stringify(item))
}.loop(true).autoPlay(true)//.autoPlay(this.slide ? false : true).interval(3000).indicator(false)

适配折叠屏,在 aboutToAppear 生命周期函数中获取设备是否可折叠,并且同时获取折叠状态,通过设备类型以及折叠状态设置 Swiper 的宽高值。同时绑定 change 回调事件,当页面变化时,触发回调实时刷新折叠状态值。

aboutToAppear(): void {
    try {
        this.isFoldable = display.isFoldable();
        // Get the foldable screen status
        let foldStatus: display.FoldStatus = display.getFoldStatus();
        if (this.isFoldable) {
            this.foldStatus = foldStatus;
            let callback: Callback<number> = () => {
                let data: display.FoldStatus = display.getFoldStatus();
                this.foldStatus = data;
            }
            // Monitor the changes in the unfolded status of the foldable screen display.on('change', callback);
        }
    } catch (error) {
        let err = error as BusinessError; hilog.error(0x0000, 'ImageSwitch', `getFoldStatus failed. code=${err.code}, message=${err.message}`);
    }
    let list: PhotoData[] = [];
    for (let i = 1; i <= 7; i++) {
        let newPhotoData = new PhotoData();
        newPhotoData.id = i;
        list.push(newPhotoData);
    }
    this.progressData = list;
    this.data = new DataSource(list);
    // ...
}

代码逻辑走读:当折叠状态取值为 2 时,表示当前折叠状态为折叠。

实现轮播图的叠加效果

场景描述

轮播图的叠加效果通过视觉层叠、内容交互动画,能显著提升信息传达的丰富性和用户体验的沉浸感。通常情况下,轮播图是按照顺序依次轮播,但是有时候也希望能够以一种重叠的方式进行展示,即当前展示的图片覆盖在前一个图片上方,给用户一种更加流畅的切换体验。

效果如图所示。

效果示意图

实现原理

使用层叠布局 (Stack) 可以实现图片的叠加,结合滑动手势来实现图片的切换,并通过 animateTo 添加相应的切换动画。通过给图片添加以下属性来实现。

  • zIndex 属性来设置组件的堆叠顺序,Index 值越大,显示层级越高,即 zIndex 值大的组件会覆盖在 zIndex 值小的组件上方。
  • visibility 属性来控制显示或隐藏,值为 Visibility.Hidden 时,表示隐藏,但参与布局进行占位;值为 Visibility.Visible 时,表示显示;值为 Visibility.None 时,表示隐藏,但不参与布局,不进行占位。
  • scale 属性来控制是否缩放,可以分别设置 X 轴、Y 轴、Z 轴的缩放比例,默认值为 1。

开发步骤

  1. Stack 布局初始化:使用 Stack 布局容器,并设置其 alignContent 属性为 Alignment.Start,这意味着子组件将从起始位置对齐。
  2. ForEach 循环:遍历 this.data 数组,为每个元素创建一个 Column 组件。
    • Column 组件:每个 Column 包含一个 Text 组件,显示数组中的字符串元素。
    • 样式和位置属性:为每个 Column 设置 zIndex、visibility、opacity、backgroundColor、width、height、offset 和 scale 属性,这些属性根据数组中的对应值动态设置。
    • 唯一键生成:使用 `(item: string, index: number) => ${item}index`` 作为 ForEach 的键生成函数,确保每个组件的唯一性。
  3. onAppear 事件:在组件首次出现时触发,设置 this.isAppear 为 true,表示组件已经出现。
  4. 手势开始时的处理:
    • 当用户开始滑动时,清除任何正在运行的定时器,并将状态变量 isStart 设置为 false。同时,设置某个数组元素的可见性为 Visibility.Visible,表示某个卡片或界面元素现在可见。
  5. 手势进行时的处理:
    • 获取滑动事件的水平偏移量 offsetX,并根据这个值计算出一个缩放因子 distanceScl。这个缩放因子用于调整当前页卡及其下一层页卡的显示效果,实现滑动时的动画效果。
    • 调用 changeSubPageWhenUpdate 方法,更新第二层和第三层页卡的显示状态,使用之前计算的 distanceScl 作为参数。
    • 再次调用 changeSubPageWhenUpdate 方法,更新第四层页卡的显示状态,这次不使用 distanceScl 调整透明度,而是根据是否达到阈值决定是否切换页面。
  6. 手势结束时的处理:
    • 获取滑动事件的速度 velocityX,并根据滑动距离和速度决定接下来执行的操作。
    • 如果滑动距离小于某个阈值 OFFSET_DISTANCE_4_SWICH_THREHOLD,则认为用户想要切换页面,更新可见性状态。
    • 如果滑动距离大于阈值,或者滑动速度超过某个阈值 SWITCH_PAGE_VELOCITY_THREHOLD,则根据滑动方向执行页面切换或淡出效果。
    • 使用之前计算的 distanceScl 作为参数。
    • 再次调用 changeSubPageWhenUpdate 方法,更新第四层页卡的显示状态,这次不使用 distanceScl 调整透明度,而是根据是否达到阈值决定是否切换页面。

在 Stack 上监听左右滑动手势,在手势的回调里面进行图片的动效切换。

.gesture(PanGesture(this.panOption).onActionStart((event: GestureEvent) => {
    clearInterval(timerId); // Stop looping playback
    this.isStart = false;
    this.visibleArray[this.currentIndexArray[3]] = Visibility.Visible;
}).onActionUpdate((event: GestureEvent) => {
    if (!event) { return; }
    let distanceScl: number = 0;
    let index0 = this.currentIndexArray[PageIndex.FIRSTPAGE];
    // The animation effect of the top card
    this.offsetXArray[index0] = event.offsetX;
    if (this.offsetXArray[index0] < 0) {
        // left
        distanceScl = this.offsetXArray[index0] > -OFFSET_DISTANCE_4_FADE_THREHOLD ? 1.0 + this.offsetXArray[index0] / OFFSET_DISTANCE_4_FADE_THREHOLD : 0;
    } else {
        // right
        distanceScl = this.offsetXArray[index0] < OFFSET_DISTANCE_4_FADE_THREHOLD ? 1.0 - this.offsetXArray[index0] / OFFSET_DISTANCE_4_FADE_THREHOLD : 0;
    }
    // The animation effect of the second-layer card
    let index1 = this.currentIndexArray[PageIndex.SCENDPAGE];
    this.changeSubPageWhenUpdate(index1, PageIndex.SCENDPAGE, distanceScl, true, true);
    // The animation effect of three layers of cards
    let index2 = this.currentIndexArray[PageIndex.THRIDPAGE];
    this.changeSubPageWhenUpdate(index1, PageIndex.THRIDPAGE, distanceScl, true, true);
    // The animation effect of four layers of cards
    let index3 = this.currentIndexArray[PageIndex.FOURTHPAGE];
    this.changeSubPageWhenUpdate(index1, PageIndex.FOURTHPAGE, distanceScl, false, true);
}).onActionEnd((event: GestureEvent) => {
    // Lift your finger
    if (!event) { return; }
    this.getUIContext().animateTo({
        duration: 200, curve: Curve.Linear,
        onFinish: () => {
            // After the animation effect ends, assign status values to each card to ensure that every component is in the correct state
            // Within the range that triggers the switch page
            if (Math.abs(this.offsetXArray[this.currentIndexArray[PageIndex.FIRSTPAGE]]) < OFFSET_DISTANCE_4_SWICH_THREHOLD) {
                this.visibleArray[this.currentIndexArray[PageIndex.FIRSTPAGE]] = Visibility.Visible;
                this.visibleArray[this.currentIndexArray[PageIndex.FOURTHPAGE]] = Visibility.None;
            } else {
                // Update the status outside the range that triggers the switch page
                this.changePagePropertyWhenFinished();
            }
        }
    }, () => {
        if (this.offsetXArray[this.currentIndexArray[PageIndex.FIRSTPAGE]] > OFFSET_DISTANCE_4_SWICH_THREHOLD || event.velocityX > SWITCH_PAGE_VELOCITY_THREHOLD) {
            // Fade out of the page to the right
            this.changePageWhenEnd(OFFSET_DISTANCE_4_FADE_THREHOLD, true, true);
        } else if (this.offsetXArray[this.currentIndexArray[PageIndex.FIRSTPAGE]] < -OFFSET_DISTANCE_4_SWICH_THREHOLD || event.velocityX < -SWITCH_PAGE_VELOCITY_THREHOLD) {
            // Fade out of the page to the left
            this.changePageWhenEnd(OFFSET_DISTANCE_4_FADE_THREHOLD, true, false);
        } else {
            this.changePageWhenEnd(0, false, true); // Return
        }
    })
}))

使用 Stack 组件将图片进行层叠布局,并给其设置 zIndex、visibility、scale 等属性。

Stack({ alignContent: Alignment.Start }) {
    ForEach(this.data, (item: string, index: number) => {
        Column() {
            Text(item).fontSize(30).fontColor(Color.Blue)
        }.zIndex(this.zIndexArray[index]).visibility(this.visibleArray[index]).opacity(this.opacityArray[index]).backgroundColor(Color.Pink).width(this.sizeArray[index].width).height(this.sizeArray[index].height).offset({ x: this.offsetXArray[index], y: 0 }).scale(this.scaleArray[index])
    }, (item: string, index: number) => `${item}${index}`)
}.onAppear(() => {
    // It is called only once after mounting
    this.isAppear = true;
})

目录

  1. Swiper 组件实现复杂轮播图
  2. 概述
  3. 使用 Swiper 实现图文作品合集
  4. 场景描述
  5. 实现原理
  6. 开发步骤
  7. 实现轮播图的叠加效果
  8. 场景描述
  9. 实现原理
  10. 开发步骤

更多推荐文章

查看全部
  • 本地搭建带知识库的 AI 助手(Ollama + Open WebUI)
  • Flutter 三方库 whatsapp_bot_flutter 鸿蒙适配与实战指南
  • 基于 AI 辅助的在线考试系统全流程开发实战
  • Web 自动化测试入门:核心概念与 Selenium 实战指南
  • 基于 Trae IDE 实现 Figma 设计稿自动转前端代码
  • Java 开发高频 Linux 命令实战:排查、部署与运维
  • 把 AI 数学公式稳妥复制到 Word 的几种办法
  • Python 基于 Flask 的小区停车场管理系统设计与实现
  • VR、具身智能与人形机器人:从虚拟训练到现实执行
  • 动态规划基础概念及第 N 个泰波那契数题解 (1)
  • cJSON 1.7.19 源码深度分析:数据结构与解析实现
  • 将 Zed 编辑器集成到 Bright Data Web MCP
  • Linux 进程终止:退出场景、方法与退出码详解
  • 人工智能:多模态大模型原理与跨模态应用实战
  • LangFlow 集成 Stable Diffusion 插件实现云端 GPU 秒级出图
  • Java 并发核心:单例模式、生产者消费者、定时器及线程池实现
  • 小白如何入门网络安全?学习路线与方向指南
  • JavaScript 条件语句与循环结构详解
  • PyTorch 生成式人工智能:循环神经网络详解与实现
  • Ubuntu 安装 OpenClaw 并接入飞书机器人

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online