鸿蒙HarmonyOS开发:基于Swiper组件和自定义指示器实现多图片进度条轮播功能

文章目录

      • 一、概述
        • 1、场景介绍
        • 2、技术选型
      • 二、实现方案
        • 1、图片区域实现
        • 2、底部导航点设计
        • 3、手动切换
      • 三、所有代码
        • 1、设置沉浸式
        • 2、外层Tabs效果
        • 3、ImageSwiper组件
      • 四、效果展示

一、概述

在短视频平台上,经常可以见到多图片合集。它的特点是:由多张图片组成一个合集,图片可以自动进行轮播,也可以手动去进行图片切换。自动轮播时,图片下方的进度条缓慢加载至完成状态;手动切换时,图片下方的进度条瞬间切换至已完成状态或未完成状态。

由于原生Swiper组件自带的导航点指示器目前只支持数字和圆点的样式,不支持对应的特殊样式,因此需要通过自定义指示器(即进度条)来模拟底部的导航条效果。

在这里插入图片描述在这里插入图片描述

1、场景介绍

常见的图文作品,可以自动循环播放和手动切换播放合集中的图片。

  • 当作品自动播放时,图片每过几秒会自动切换到下一张,且下方进度条进度与该图片的停留时间匹配。

  • 当作品手动播放时,下方进度条会跟着图片的滑动切换而改变成未完成状态或已完成状态。

2、技术选型

从技术角度看,图文作品轮播效果可通过Swiper组件和它的指示器的联动效果实现,由于Swiper组件的指示器无法自定义,所以需要拆开实现:

  • 上面图片的轮播部分继续使用Swiper组件实现。

  • 下面的指示器,由于Swiper组件只有两种显示模式,一个是圆点,一个是数字,很明显是不能实现进度条的效果。所以需要关闭原生指示器,自定义一个指示器。

二、实现方案

1、图片区域实现

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

  • 通过设置loop属性控制是否循环播放,该属性默认值为true。当loop为true时,在显示第一页或最后一页时,可以继续往前切换到前一页或者往后切换到后一页。如果loop为false,则在第一页或最后一页时,无法继续向前或者向后切换页面。

  • 通过设置autoPlay属性,控制是否自动轮播子组件。该属性默认值为false。autoPlay为true时,会自动切换播放子组件。

  • 通过设置interval属性,控制子组件与子组件之间的播放间隔。interval属性默认值为3000,单位毫秒。

  • 通过设置indicator属性为false,来关闭Swiper组件自带的导航点指示器样式。

  • 通过设置indicatorInteractive属性为false,来设置禁用组件导航点交互功能。

Swiper(this.swiperController) {LazyForEach(this.data, (item: PhotoData) => {Image($r(`app.media.` + item.id)).width('100%').height('100%').objectFit(ImageFit.Cover)}, (item: PhotoData) => JSON.stringify(item))
}
.width('100%')
.height('100%')
.autoPlay(true)
.indicator(false)
.loop(false)
.indicatorInteractive(false)
.duration(300)
.curve(Curve.Linear)

示意效果如下图所示。

在这里插入图片描述

2、底部导航点设计

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

  • 使用层叠布局 (Stack),配合Row容器来实现进度条的布局。

  • 要实现进度条缓慢增长至完成状态且用时与图片播放时间相匹配的效果,可以给Row容器组件添加属性动画 (animation),设置duration(动画持续时间)与图片播放时间匹配即可。

  • 进度条状态切换:通过播放图片的currentIndex与进度条的index进行比较,当currentIndex大于或等于index时,需要将进度条样式设置成已完成状态,否则是未完成状态。可以通过设置完成时进度条的背景颜色为Color.White或Color.Grey来实现两种样式的进度条切换。

创建自定义组件progressComponent。

  @BuilderprogressComponent() {Row({ space: 5 }) {ForEach(this.progressData, (item: PhotoData, index: number) => {Stack({ alignContent: Alignment.Start }) {// 底层灰色Row().zIndex(0).width('100%').height(2).borderRadius(2).backgroundColor(Color.Grey)//上层白色Row().zIndex(1).width(this.currentIndex >= index ? '100%' : 0).height(2).borderRadius(2).backgroundColor(Color.White).animation({duration: this.duration - 400,curve: Curve.Linear,iterations: 1,playMode: PlayMode.Normal,onFinish: () => {if (this.currentIndex === this.progressData.length - 1) {this.duration = 400;this.currentIndex = -1;}}})}.layoutWeight(1)}, (item: PhotoData) => JSON.stringify(item))}.width('100%').height(40)}

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

示意效果如下图所示。

在这里插入图片描述

3、手动切换

当图片集合手动播放时,随着图片的切换,下方进度条会跟随着切换为已完成状态或未完成状态。此时,开发者需要给Swiper组件添加onGestureSwipe事件,来判断页面是否跟手滑动。

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

slide为布尔值,用来判断页面是否跟手滑动。默认值为false,当页面跟手滑动时,slide的值为true。

然后根据slide是否为手动滑动来判断:是否循环播放,是否自动轮播,进度条动画效果等功能。

三、所有代码

  • 外层包个Tabs实现仿抖音效果。

  • 上面图片的轮播部分使用Swiper组件实现。

  • 下面的指示器,需要关闭原生指示器,自定义指示器(进度条)来实现。

  • 设置窗口的布局为沉浸式布局,设置状态栏文字颜色为白色。

1、设置沉浸式

// EntryAbility.ets

onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');// 获取该WindowStage实例下的主窗口。const mainWindow = windowStage.getMainWindowSync();// 设置主窗口的布局是否为沉浸式布局。mainWindow.setWindowLayoutFullScreen(true).then(() => {hilog.info(0x0000, 'testTag', 'Succeeded in setting the window layout to full-screen mode');}).catch((err: BusinessError) => {hilog.info(0x0000, 'testTag', 'Failed to set the window layout to full-screen mode. Cause: %{public}s', JSON.stringify(err) ?? '');})// 状态栏文字颜色。const sysBarProps: window.SystemBarProperties = {statusBarContentColor: '#ffffff'};// 设置主窗口三键导航栏、状态栏的属性。mainWindow.setWindowSystemBarProperties(sysBarProps).then(() => {hilog.info(0x0000, 'testTag', 'Succeeded in setting the system bar properties');}).catch((err: BusinessError) => {hilog.info(0x0000, 'testTag', 'Failed to set system bar properties. Cause: %{public}s', JSON.stringify(err) ?? '');})// ........
}
2、外层Tabs效果

其他文章有详细介绍Tabs效果的具体实现。此处不再赘述。

// MultipleImagePage.ets

import { ImageSwiper } from "./ImageSwiper";interface TabBar {icon?: Resourcetext?: string
}//自定义tabBar样式
@Extend(Column)
function tabBarContainerStyle() {.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor(Color.Transparent)
}@Entry
@Component
struct MultipleImagePage {@State selectedTabIndex: number = 0private tabBars: TabBar[] = [{ text: '首页' },{ text: '朋友' },{ text: '发布', icon: $r('app.media.add') },{ text: '消息' },{ text: '我' }]//文字tabBar@BuilderTabBarTextBuilder(tabBarText: string, tabIndex: number) {Column() {Text(tabBarText).fontColor(Color.White).opacity(this.selectedTabIndex === tabIndex ? 1 : 0.6)}.tabBarContainerStyle()}//中间图片tabBar@BuilderTabBarIconBuilder(icon: Resource) {Column() {Image(icon).width(36).margin({bottom:8})}.tabBarContainerStyle()}build() {Tabs({ barPosition: BarPosition.End }) {ForEach(this.tabBars, (tabBar: TabBar, index) => {TabContent() {if (index === 0) {ImageSwiper()} else {Column() {Text(tabBar.text).fontColor(Color.White).fontSize(40)}.width('100%').height('100%').alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).backgroundColor(Color.Black)}}.tabBar(tabBar.icon ?this.TabBarIconBuilder(tabBar.icon) :this.TabBarTextBuilder(tabBar.text, index))}, (tabBar: TabBar) => JSON.stringify(tabBar))}.barOverlap(true) // 设置TabBar是否背后变模糊并叠加在TabContent之上。.barHeight(56) // 设置TabBar的高度值。.backgroundColor(Color.Transparent).barBackgroundColor(Color.Transparent) // 设置TabBar的背景颜色。.barBackgroundBlurStyle(BlurStyle.NONE) // 设置TabBar的背景模糊材质。关闭模糊.divider({ strokeWidth: 1, color: 'rgba(255, 255, 255, 0.20)' }) // 设置区分TabBar和TabContent的分割线样式。.onChange((index: number) => {this.selectedTabIndex = index}).hitTestBehavior(HitTestMode.Transparent) // 设置组件的触摸测试类型。自身和子节点都响应触摸测试,不会阻塞兄弟节点的触摸测试。不会影响祖先节点的触摸测试。}
}
3、ImageSwiper组件
import { DataSource, PhotoData } from '../model/ImageData'
import { OperateButton } from "./OperateButton";@Extend(Text)
function videoInfoStyle() {.fontSize(14).fontColor('rgba(255, 255, 255, 0.80)')
}@Component
export struct ImageSwiper {private swiperController: SwiperController = new SwiperController();@State progressData: PhotoData[] = [];@State data: DataSource = new DataSource([]);@State currentIndex: number = -1;@State duration: number = 3000;@State slide: boolean = false;// 进度条@BuilderprogressComponent() {Row({ space: 5 }) {ForEach(this.progressData, (item: PhotoData, index: number) => {Stack({ alignContent: Alignment.Start }) {Row().zIndex(0).width('100%').height(2).borderRadius(2).backgroundColor(Color.Grey)Row().zIndex(1).width(this.currentIndex >= index && !this.slide ? '100%' : 0).height(2).borderRadius(2).backgroundColor(Color.White).animation(!this.slide ? {duration: this.duration - 400,curve: Curve.Linear,iterations: 1,playMode: PlayMode.Normal,onFinish: () => {if (this.currentIndex === this.progressData.length - 1) {this.duration = 400;this.currentIndex = -1;}}} : { duration: 0 })Row().zIndex(2).width(this.currentIndex >= index && this.slide ? '100%' : 0).height(2).borderRadius(2).backgroundColor(Color.White)}.layoutWeight(1)}, (item: PhotoData) => JSON.stringify(item))}.width('100%').height(40).margin({ bottom: 60 })}//底部文字@BuilderbottomTextComponent() {Column({ space: 15 }) {Row({ space: 10 }) {Text('@山猫').fontColor(Color.White)Text('2024-12-23 14:52').videoInfoStyle()}Text('海的那边,是迷雾中的诗,是浪尖上的歌,还是梦里的远方').videoInfoStyle()}.padding(16).width('80%').alignItems(HorizontalAlign.Start).margin({ right: '20%', bottom: 100 }).hitTestBehavior(HitTestMode.Transparent)}//右侧操作栏@BuilderrightOperateComponent() {Column({ space: 20 }) {OperateButton({head: $r('app.media.user'),likeCount: 123,commentCount: 234,collectCount: 345,shareCount: 456})}.width('20%').padding(16).margin({ bottom: 100 })}//轮播数据创建aboutToAppear() {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);}build() {Stack({ alignContent: Alignment.BottomEnd }) {//轮播图片Swiper(this.swiperController) {LazyForEach(this.data, (item: PhotoData) => {Image($r(`app.media.` + item.id)).width('100%').height('100%').objectFit(ImageFit.Cover)}, (item: PhotoData) => JSON.stringify(item))}.width('100%').height('100%').autoPlay(!this.slide ? true : false).indicator(false).loop(!this.slide ? true : false).indicatorInteractive(false).duration(400).curve(Curve.Linear).onChange((index) => {this.duration = 3000;this.currentIndex = index;}).onAppear(() => {this.currentIndex = 0;}).onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {this.slide = true;})// 底部文字描述this.bottomTextComponent();// 右侧操作栏this.rightOperateComponent()// 进度条this.progressComponent();}}
}

四、效果展示

  • 运行应用后,不滑动屏幕时,图片自动轮播,且下方进度条缓慢增长至已完成状态,播放完成时,会继续循环播放。

  • 滑动屏幕时,图片跟随滑动方向而进行切换,此时会关闭自动轮播和循环播放的效果,且下方进度条瞬间增长至已完成状态。

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/502831.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JVM】总结篇-类的加载篇之 类的加载器 和ClassLoader分析

文章目录 类的加载器ClassLoader自定义类加载器双亲委派机制概念源码分析优势劣势如何打破Tomcat 沙箱安全机制JDK9 双亲委派机制变化 类的加载器 获得当前类的ClassLoader clazz.getClassLoader() 获得当前线程上下文的ClassLoader Thread.currentThread().getContextClassLoa…

nginx学习之路-nginx配置https服务器

文章目录 1. 生成证书2. 配置证书1. 拷贝证书文件2. 修改conf/nginx.conf文件内容 3. 查看效果1. 重载配置2. 访问 1. 生成证书 在linux系统下执行&#xff0c;使用openssl命令。&#xff08;windows环境也可以使用cmder&#xff09; # 1. 生成私钥 server2025.key(无密码保护…

鸿蒙应用开发搬砖经验之—使用DevTools工具调试前端页面

环境说明&#xff1a; 系统环境&#xff1a;Mac mini M2 14.5 (23F79) 开发IDE&#xff1a;DevEco Studio 5.0.1 Release 配置步骤&#xff1a; 按着官方的指引来慢慢一步一步来&#xff0c;但前提是要配置好SDK的路径&#xff08;没有配置的话&#xff0c;可能先看下面的配…

【NLP高频面题 - 分布式训练篇】ZeRO主要为了解决什么问题?

【NLP高频面题 - 分布式训练篇】ZeRO主要为了解决什么问题&#xff1f; 重要性&#xff1a;★★ 零冗余优化器技术由 DeepSpeed 代码库提出&#xff0c;主要用于解决数据并行中的模型冗余问题&#xff0c;即每张 GPU 均需要复制一份模型参数。 ZeRO的全称是Zero Redundancy …

《探秘计算机视觉与深度学习:开启智能视觉新时代》

《探秘计算机视觉与深度学习&#xff1a;开启智能视觉新时代》 一、追溯起源&#xff1a;从萌芽到崭露头角二、核心技术&#xff1a;解锁智能视觉的密码&#xff08;一&#xff09;卷积神经网络&#xff08;CNN&#xff09;&#xff1a;图像识别的利器&#xff08;二&#xff0…

[paddle] 非线性拟合问题的训练

利用paddlepaddle建立神经网络&#xff0c;模拟有限个数据的非线性拟合 本文仍然考虑 f ( x ) sin ⁡ ( x ) x f(x)\frac{\sin(x)}{x} f(x)xsin(x)​ 函数在区间 [-10,10] 上固定数据的拟合。 import paddle import paddle.nn as nn import numpy as np import matplotlib.…

详解GPT-信息抽取任务 (GPT-3 FAMILY LARGE LANGUAGE MODELS)

GPT-3 FAMILY LARGE LANGUAGE MODELS Information Extraction 自然语言处理信息提取任务&#xff08;NLP-IE&#xff09;&#xff1a;从非结构化文本数据中提取结构化数据&#xff0c;例如提取实体、关系和事件 [164]。将非结构化文本数据转换为结构化数据可以实现高效的数据处…

逆向入门(2)C篇-基础知识

C基础 1、在C中&#xff0c;函数的变量是从右往左传递的&#xff0c;也就是test(x,y)&#xff0c;先传入y&#xff0c;再传x。 2、变量的分类&#xff1a; &#xff08;1&#xff09;全局变量。在编译的时候就已经确定了内存地址和宽度&#xff0c;变量名就是内存地址的别名…

服务器数据恢复—离线盘数超过热备盘数导致raidz阵列崩溃的数据恢复

服务器数据恢复环境&故障&#xff1a; 一台配有32块硬盘的服务器在运行过程中突然崩溃不可用。经过初步检测&#xff0c;基本上确定服务器硬件不存在物理故障。管理员重启服务器后问题依旧。需要恢复该服务器中的数据。 服务器数据恢复环境&#xff1a; 1、将服务器中硬盘…

Echart实现3D饼图示例

在可视化项目中&#xff0c;很多地方会遇见图表&#xff1b;echart是最常见的&#xff1b;这个示例就是用Echart&#xff0c; echart-gl实现3D饼图效果&#xff0c;复制即可用 //需要安装&#xff0c;再引用依赖import * as echarts from "echarts"; import echar…

PostgreSQL学习笔记(一):PostgreSQL介绍和安装

目录 概念 PostgreSQL简介 PostgreSQL的关键特性 1. 标准兼容性 2. 扩展性 3. 数据完整性和可靠性 4. 丰富的数据类型 5. 查询能力 6. 事务和并发控制 7. 扩展和插件 8. 跨平台和多语言支持 9. 高可用性和扩展性 常用场景 安装 Linux apt安装 下载安装包安装 客…

Linux之信号量

目录 信号量 信号量相关接口 创建信号量 初始化信号量 等待信号量&#xff0c;P操作 发布信号量&#xff0c;V操作 销毁信号量 基于信号量的环形队列下的生产者和消费者模型 环形队列 代码实现 上期我们学习了线程同步的概念&#xff0c;掌握了基于阻塞队列的生产…

Redis--高可用(主从复制、哨兵模式、分片集群)

高可用&#xff08;主从复制、哨兵模式、分片集群&#xff09; 高可用性Redis如何实现高可用架构&#xff1f;主从复制原理1. 全量同步2. 命令传播3. 增量同步 Redis Sentinel&#xff08;哨兵模式&#xff09;为什么要有哨兵模式&#xff1f;哨兵机制是如何工作的&#xff1f;…

常用的数据结构API概览

List ArrayList 1、在初始化一个ArrayList的时候&#xff0c;如果我想同时set一些值 比如存放int[ ] List<int[]> list new ArrayList(Arrays.asList(new int[]{intervals[0][0],intervals[0][1]}));//或者int[] temp new int[]{intervals[0][0],intervals[0][1]}…

wordpress右侧浮动咨询台插件

简洁实用&#xff0c;操作方便&#xff0c;没有复杂的设置。 下载、安装、启用&#xff0c;即可使用。 wordpress在线客服插件-CS4&#xff0c;该插件适用于简站主题与精智主题。 下载 https://www.jianzhanpress.com/?p4622

Spring MVC实战指南:构建高效Web应用的架构与技巧(三)

响应数据和结果视图(7种) 返回值分类 创建web.xml&#xff08;spring、过滤器解决乱码、配置控制器dispatcherServlet、加载springmvc.xml文件、配置启动加载&#xff09;创建springmvc.xml文件 <!--配置了内容&#xff0c;启动Tomcat服务器的时候&#xff0c;就会被加载--…

使用LINUX的dd命令制作自己的img镜像

为了避免重复安装同一镜像&#xff0c;配置环境&#xff0c;首先我准备一个正常使用的完整系统。 使用Gparted软件先将母盘&#xff08;如U盘&#xff0c;TF卡&#xff09;分区调整为只有数据的大小。如&#xff1a;60G的TF卡&#xff0c;只用了3.5G&#xff0c;将未使用的空间…

doris:基于 Arrow Flight SQL 的高速数据传输链路

Doris 基于 Arrow Flight SQL 协议实现了高速数据链路&#xff0c;支持多种语言使用 SQL 从 Doris 高速读取大批量数据。 用途​ 从 Doris 加载大批量数据到其他组件&#xff0c;如 Python/Java/Spark/Flink&#xff0c;可以使用基于 Arrow Flight SQL 的 ADBC/JDBC 替代过去…

Gitee图形界面上传(详细步骤)

目录 1.软件安装 2.安装顺序 3.创建仓库 4.克隆远程仓库到本地电脑 提交代码的三板斧 1.软件安装 Git - Downloads (git-scm.com) Download – TortoiseGit – Windows Shell Interface to Git 2.安装顺序 1. 首先安装git-2.33.1-64-bit.exe&#xff0c;顺序不能搞错2. …

用公网服务代理到本地电脑笔记

参考&#xff1a; 利用frp 穿透到内网的http/https网站&#xff0c;实现对外开放&#xff08;这篇博客有点老&#xff0c;需要改动&#xff0c;不能照抄&#xff09;&#xff1a;https://www.cnblogs.com/hahaha111122222/p/8509150.html frp内网穿透(windows和服务器)&#xf…