用 HarmonyOS 做一个可以手势控制的电子相册应用(ArkTS)

2023-10-23 12:04

本文主要是介绍用 HarmonyOS 做一个可以手势控制的电子相册应用(ArkTS),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

本篇 Codelab 介绍了如何实现一个简单的电子相册应用,主要功能包括:

1.  实现首页顶部的轮播效果。

2.  实现页面多种布局方式。

3.  实现通过手势控制图片的放大、缩小、左右滑动查看细节等效果。

相关概念

● Swiper:滑块视图容器,提供子组件滑动轮播显示的能力。

● Grid:网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。

● Navigation:Navigation 组件一般作为 Page 页面的根容器,通过属性设置来展示页面的标题、工具栏、菜单。

● List:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。

● 组合手势:手势识别组,多种手势组合为复合手势,支持连续识别、并行识别和互斥识别。

完整示例

gitee源码地址

源码下载

电子相册(ArkTS).zip

环境搭建

我们首先需要完成 HarmonyOS 开发环境搭建,可参照如下步骤进行。

软件要求

● DevEco Studio版本:DevEco Studio 3.1 Release。

● HarmonyOS SDK版本:API version 9。

相关概念

● Swiper:滑块视图容器,提供子组件滑动轮播显示的能力。

● Grid:网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。

● Navigation:Navigation 组件一般作为 Page 页面的根容器,通过属性设置来展示页面的标题、工具栏、菜单。

● List:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。

● 组合手势:手势识别组,多种手势组合为复合手势,支持连续识别、并行识别和互斥识别。

完整示例

gitee源码地址

源码下载

电子相册(ArkTS).zip

环境搭建

我们首先需要完成 HarmonyOS 开发环境搭建,可参照如下步骤进行。

软件要求

● DevEco Studio版本:DevEco Studio 3.1 Release。

● HarmonyOS SDK版本:API version 9。

硬件要求

● 设备类型:华为手机或运行在 DevEco Studio 上的华为手机设备模拟器。

● HarmonyOS 系统:3.1.0 Developer Release。

环境搭建

1.  安装 DevEco Studio,详情请参考下载和安装软件。

2.  设置 DevEco Studio 开发环境,DevEco Studio 开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:如果可以直接访问 Internet,只需进行下载HarmonyOS SDK操作。

a.  如果网络不能直接访问 Internet,需要通过代理服务器才可以访问,请参考配置开发环境。

3.  开发者可以参考以下链接,完成设备调试的相关配置:使用真机进行调试

a.  使用模拟器进行调试

代码结构解读

本篇 Codelab 只对核心代码进行讲解,对于完整代码,我们会在源码下载或 gitee 中提供。

├──entry/src/main/ets                // 代码区│  ├──common│  │  ├──constansts│  │  │  └──Constants.ets            // 常量类│  │  └──utils│  │     └──Logger.ets               // Logger公共类│  ├──entryability│  │  └──EntryAbility.ts             // 程序入口类│  ├──pages│  │  ├──DetailListPage.ets          // 图片详情页面│  │  ├──DetailPage.ets              // 查看大图页面│  │  ├──IndexPage.ets               // 电子相册主页面│  │  └──ListPage.ets                // 图片列表页面│  └──view│     └──PhotoItem.ets               // 首页相册Item组件└──entry/src/main/resources          // 资源文件

构建应用页面

应用首页用 Column 组件来实现纵向布局,从上到下依次是标题组件 Text、轮播图 Swiper、相册列表 Grid。标题和轮播图均设置固定高度,底部相册列表通过 layoutWeight 属性实现自适应布局占满剩余空间。

// IndexPage.etsColumn() {  Row() {    Text($r('app.string.EntryAbility_label'))  }
  Swiper(this.swiperController) {    ForEach(Constants.BANNER_IMG_LIST, (item: Resource, index?: number) => {      Row() {        Image(item)        ...      }    }, (item: Resource, index?: number) => JSON.stringify(item) + index)  }  ...
  Grid() {    ForEach(IMG_ARR, (photoArr: Array<Resource>, index?: number) => {      GridItem() {        PhotoItem({ photoArr })      }      ...      .onClick(() => {        router.pushUrl({          url: Constants.URL_LIST_PAGE,          params: { photoArr: JSON.stringify(photoArr) }        });      })    }, (item: Array<Resource>, index?: number) => JSON.stringify(item) + index)  }  .columnsTemplate(Constants.INDEX_COLUMNS_TEMPLATE)  .columnsGap($r('app.float.grid_padding'))  .rowsGap($r('app.float.grid_padding'))  .padding({ left: $r('app.float.grid_padding'), right: $r('app.float.grid_padding') })  .layoutWeight(1)}

图片列表页是网格状展开的图片列表,主要使用 Grid 组件和 GridItem 组件,GridItem 高度通过 aspectRatio 属性设置为跟宽度一致。

// ListPage.etsNavigation() {  Grid() {    ForEach(this.photoArr, (img: Resource, index?: number) => {      GridItem() {        Image(img)          .onClick(() => {            if (!index) {              index = 0;            }            this.selectedIndex = index;            router.pushUrl({              url: Constants.URL_DETAIL_LIST_PAGE,              params: {                photoArr: JSON.stringify(this.photoArr),              }            });          })      }      ...      .aspectRatio(1)    }, (item: Resource) => JSON.stringify(item))  }  .columnsTemplate(Constants.GRID_COLUMNS_TEMPLATE) // 每行显示4个item  ...  .layoutWeight(1)}

图片详情页由上下两个横向滚动的 List 组件完成整体布局,两个组件之间有联动的效果。上边展示的大图始终是底部 List 处于屏幕中间位置的图片。滚动或者点击底部的 List,上边展示的大图会随着改变,同样左右滑动上边的图片时,底部 List 组件也会随之进行滚动。

// DetailListPage.etsStack({ alignContent: Alignment.Bottom }) {  // 上部大图列表组件  List({ scroller: this.bigScroller, initialIndex: this.selectedIndex }) {    ForEach(this.photoArr, (img: Resource) => {      ListItem() {        Image(img)          ...          .gesture(PinchGesture({ fingers: Constants.DOUBLE_NUMBER })            .onActionStart(() => this.goDetailPage()))          .onClick(() => this.goDetailPage())      }    }, (item: Resource) => JSON.stringify(item))  }  ...  .onScroll((scrollOffset, scrollState) => {    if (scrollState === ScrollState.Fling) {      this.bigScrollAction(scrollTypeEnum.SCROLL);    }  })  .onScrollStop(() => this.bigScrollAction(scrollTypeEnum.STOP))  // 底部小图列表组件  List({     scroller: this.smallScroller,     space: Constants.LIST_ITEM_SPACE,    initialIndex: this.selectedIndex    }) {    ForEach(this.smallPhotoArr, (img: Resource, index?: number) => {      ListItem() {        this.SmallImgItemBuilder(img, index)      }    }, (item: Resource) => JSON.stringify(item))  }  ...  .listDirection(Axis.Horizontal)  .onScroll((scrollOffset, scrollState) => {    if (scrollState === ScrollState.Fling) {      this.smallScrollAction(scrollTypeEnum.SCROLL);    }  })  .onScrollStop(() => this.smallScrollAction(scrollTypeEnum.STOP))}

查看大图页面由一个横向滚动的 List 组件来实现图片左右滑动时切换图片的功能,和一个 Row 组件实现图片的缩放和拖动查看细节功能。对图片进行缩放时会从 List 组件切换成 Row 组件来实现对单张图片的操作,对单张图片进行滑动操作时,也会由 Row 组件转换为 List 组件来实现图片的切换功能。

// DetailPage.etsStack() {  List({ scroller: this.scroller, initialIndex: this.selectedIndex }) {    ForEach(this.photoArr, (img: Resource) => {      ListItem() {        Image(img)          ...          .onClick(() => router.back())      }      .gesture(gesture(GestureGroup(GestureMode.Exclusive,        PinchGesture({ fingers: Constants.DOUBLE_NUMBER })          .onActionStart(() => {            this.resetImg();            this.isScaling = true;            this.imgOffSetX = 0;            this.imgOffSetY = 0;          })          .onActionUpdate((event?: GestureEvent) => {            if (event) {              this.imgScale = this.currentScale * event.scale;            }          })          .onActionEnd(() => {            if (this.imgScale < 1) {              this.resetImg();              this.imgOffSetX = 0;              this.imgOffSetY = 0;            } else {              this.currentScale = this.imgScale;            }          }), PanGesture()          .onActionStart(() => {            this.resetImg();            this.isScaling = true;          })          .onActionUpdate((event?: GestureEvent) => {            if (event) {              this.imgOffSetX = this.preOffsetX + event.offsetX;              this.imgOffSetY = this.preOffsetY + event.offsetY;            }          })          ...      ))    }, (item: Resource) => JSON.stringify(item))  }  ...  .onScrollStop(() => {    let currentIndex = Math.round((this.scroller.currentOffset()      .xOffset + (this.imageWidth / Constants.DOUBLE_NUMBER)) / this.imageWidth);    this.scroller.scrollTo({ xOffset: currentIndex * this.imageWidth, yOffset: 0 });  })  .visibility(this.isScaling ? Visibility.Hidden : Visibility.Visible)
  Row() {    Image(this.photoArr[this.selectedIndex])    ...  }  .visibility(this.isScaling ? Visibility.Visible : Visibility.Hidden)}

通过手势控制图片

大图浏览界面双指捏合时通过改变 Image 组件的 scale 来控制图片的缩放,单手拖动时通过改变 Image 的偏移量来控制图片的位置,手势操作调用组合手势 GestureGroup 实现。其中 PinchGesture 实现双指缩放手势,PanGesture 实现单指拖动手势。

// DetailPage.ets // 手势结束时,根据边界值判断是否需要切换显示图片handlePanEnd(): void {  let initOffsetX = (this.imgScale - 1) * this.imageWidth + this.smallImgWidth;  if (Math.abs(this.imgOffSetX) > initOffsetX) {    if (this.imgOffSetX > initOffsetX && this.selectedIndex > 0) {      this.selectedIndex -= 1;    } else if (this.imgOffSetX < -initOffsetX && this.selectedIndex < (this.photoArr.length - 1)) {      this.selectedIndex += 1;    }    this.isScaling = false;    this.resetImg();    this.scroller.scrollTo({ xOffset: this.selectedIndex * this.imageWidth, yOffset: 0 });  }}
build() {  Stack() {    ...    Row() {      Image(this.photoArr[this.selectedIndex])        .position({ x: this.imgOffSetX, y: this.imgOffSetY })        .scale({ x: this.imgScale, y: this.imgScale })        ...    }    .gesture(GestureGroup(GestureMode.Exclusive,      PinchGesture({ fingers: Constants.DOUBLE_NUMBER })        .onActionUpdate((event?: GestureEvent) => {          if (event) {            this.imgScale = this.currentScale * event.scale;          }        })        .onActionEnd(() => {          // 捏合手势结束时,如果图片缩放比例小于1,则重置图片大小和位置          if (this.imgScale < 1) {            this.resetImg();            this.imgOffSetX = 0;            this.imgOffSetY = 0;          } else {            this.currentScale = this.imgScale;          }        }),      PanGesture()        .onActionStart(() => {          // 手势开始时记录图片当前位置          this.preOffsetX = this.imgOffSetX;          this.preOffsetY = this.imgOffSetY;        })        .onActionUpdate((event?: GestureEvent) => {          // 更新图片的位置          if (event) {            this.imgOffSetX = this.preOffsetX + event.offsetX;            this.imgOffSetY = this.preOffsetY + event.offsetY;          }        })        .onActionEnd(() => this.handlePanEnd())    ))    ...  }  ...}

总结

您已经完成了本次 Codelab 的学习,并了解到以下知识点:

1.  如何实现首页顶部的轮播效果。

2.  如何实现页面多种布局样式。

3.  如何通过手势控制图片的放大、缩小、左右滑动查看细节等效果。

这篇关于用 HarmonyOS 做一个可以手势控制的电子相册应用(ArkTS)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

C#通过进程调用外部应用的实现示例

《C#通过进程调用外部应用的实现示例》本文主要介绍了C#通过进程调用外部应用的实现示例,以WINFORM应用程序为例,在C#应用程序中调用PYTHON程序,具有一定的参考价值,感兴趣的可以了解一下... 目录窗口程序类进程信息类 系统设置类 以WINFORM应用程序为例,在C#应用程序中调用python程序

Java应用如何防止恶意文件上传

《Java应用如何防止恶意文件上传》恶意文件上传可能导致服务器被入侵,数据泄露甚至服务瘫痪,因此我们必须采取全面且有效的防范措施来保护Java应用的安全,下面我们就来看看具体的实现方法吧... 目录恶意文件上传的潜在风险常见的恶意文件上传手段防范恶意文件上传的关键策略严格验证文件类型检查文件内容控制文件存储

CSS3 布局样式及其应用举例

《CSS3布局样式及其应用举例》CSS3的布局特性为前端开发者提供了无限可能,无论是Flexbox的一维布局还是Grid的二维布局,它们都能够帮助开发者以更清晰、简洁的方式实现复杂的网页布局,本文给... 目录深入探讨 css3 布局样式及其应用引言一、CSS布局的历史与发展1.1 早期布局的局限性1.2

MySQL精准控制Binlog日志数量的三种方案

《MySQL精准控制Binlog日志数量的三种方案》作为数据库管理员,你是否经常为服务器磁盘爆满而抓狂?Binlog就像数据库的“黑匣子”,默默记录着每一次数据变动,但若放任不管,几天内这些日志文件就... 目录 一招修改配置文件:永久生效的控制术1.定位my.cnf文件2.添加核心参数不重启热更新:高手应

在React聊天应用中实现图片上传功能

《在React聊天应用中实现图片上传功能》在现代聊天应用中,除了文字和表情,图片分享也是一个重要的功能,本文将详细介绍如何在基于React的聊天应用中实现图片上传和预览功能,感兴趣的小伙伴跟着小编一起... 目录技术栈实现步骤1. 消息组件改造2. 图片预览组件3. 聊天输入组件改造功能特点使用说明注意事项

Redis中RedisSearch使用及应用场景

《Redis中RedisSearch使用及应用场景》RedisSearch是一个强大的全文搜索和索引模块,可以为Redis添加高效的搜索功能,下面就来介绍一下RedisSearch使用及应用场景,感兴... 目录1. RedisSearch的基本概念2. RedisSearch的核心功能(1) 创建索引(2

Python datetime 模块概述及应用场景

《Pythondatetime模块概述及应用场景》Python的datetime模块是标准库中用于处理日期和时间的核心模块,本文给大家介绍Pythondatetime模块概述及应用场景,感兴趣的朋... 目录一、python datetime 模块概述二、datetime 模块核心类解析三、日期时间格式化与

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置