63.Harmonyos NEXT 图片预览组件之手势处理实现

devtools/2025/3/15 9:19:10/

温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦!

Harmonyos NEXT 图片预览组件之手势处理实现

文章目录

  • Harmonyos NEXT 图片预览组件之手势处理实现
    • 效果预览
    • 一、手势处理概述
      • 1. 手势类型
      • 2. 手势处理架构
    • 二、单指拖动手势实现
      • 1. 手势定义
      • 2. 主轴处理
      • 3. 交叉轴处理
    • 三、双指缩放手势实现
      • 1. 手势定义
      • 2. 缩放处理逻辑
      • 3. 弹性缩放体验
    • 四、总结

效果预览

一、手势处理概述

手势处理是图片预览组件的核心交互功能,通过识别和响应用户的各种触摸操作,实现图片的缩放、旋转、拖动和切换等功能。本文将详细介绍PicturePreviewImage组件中的手势处理实现原理。

1. 手势类型

图片预览组件支持以下几种手势类型:

手势类型触发方式功能
单指拖动单指在屏幕上滑动移动图片、触发图片切换
双指缩放两指捏合或分开放大或缩小图片
双指旋转两指旋转旋转图片
双击快速点击两次在默认大小和适配屏幕大小之间切换

2. 手势处理架构

图片预览组件采用了组合手势的处理架构,通过GestureGroup将多种手势组合在一起,实现复杂的交互效果:

// 单指手势组
GestureGroup(GestureMode.Parallel,TapGesture({ count: 2 }),  // 双击手势PanGesture({ fingers: 1 })  // 单指拖动手势
)// 双指手势组
GestureGroup(GestureMode.Parallel,RotationGesture({ angle: this.imageRotateInfo.startAngle }),  // 旋转手势PinchGesture({ fingers: 2, distance: 1 })  // 缩放手势
)

二、单指拖动手势实现

1. 手势定义

PanGesture({ fingers: 1 }).onActionUpdate((event: GestureEvent) => {if (this.imageWH != ImageFitType.TYPE_DEFAULT) {if (this.eventOffsetX != event.offsetX || event.offsetY != this.eventOffsetY) {this.eventOffsetX = event.offsetX;this.eventOffsetY = event.offsetY;this.setCrossAxis(event);this.setPrincipalAxis(event);}}}).onActionEnd((event: GestureEvent) => {this.imageOffsetInfo.stash();this.evaluateBound();})

2. 主轴处理

主轴处理是单指拖动手势的核心,它负责处理图片在主滑动方向上的移动:

// 设置主轴位置
setPrincipalAxis(event: GestureEvent) {// 获取主轴方向let direction: "X" | "Y" = this.listDirection === Axis.Horizontal ? "X" : "Y";// 获取主轴中对应的是 width 还是 heightlet imageWH = this.listDirection === Axis.Horizontal ? ImageFitType.TYPE_WIDTH : ImageFitType.TYPE_HEIGHT;// 获取手指在主轴移动偏移量let offset = event[`offset${direction}`];// 获取图片最后一次在主轴移动的数据let lastOffset = imageWH === ImageFitType.TYPE_WIDTH ? this.imageOffsetInfo.lastX : this.imageOffsetInfo.lastY;// 获取主轴上图片的尺寸const IMG_SIZE = getImgSize(this.imageDefaultSize, this.imageRotateInfo.lastRotate, imageWH);const WIN_SIZE = windowSizeManager.get();// 获取窗口对应轴的尺寸const WIN_AXIS_SIZE = WIN_SIZE[imageWH];// 当前最大移动距离let maxAllowedOffset = getMaxAllowedOffset(WIN_AXIS_SIZE, IMG_SIZE, this.imageScaleInfo.scaleValue);// 计算当前移动后偏移量结果let calculatedOffset = lastOffset + offset;// 处理左右滑动边界if (offset < 0) {// 左滑if ((this.imageIndex >= this.imageMaxLength - 1) || (calculatedOffset >= -maxAllowedOffset)) {// 当是最后一个元素 或者 当前移动没有抵达边缘时候触发this.setCurrentOffsetXY(imageWH, calculatedOffset)}} else if (offset > 0) {// 右滑if ((this.imageIndex === 0) || (calculatedOffset <= maxAllowedOffset)) {// 当是第一个元素 或者 当前移动没有抵达边缘时候触发this.setCurrentOffsetXY(imageWH, calculatedOffset)}}// 处理图片切换预览if ((calculatedOffset > maxAllowedOffset) && (this.imageIndex !== 0)) {// 右滑 -- 当前滑动超过最大值时 并且 不是第一个元素去设置list偏移量显"下一张"图片let listOffset = calculatedOffset - maxAllowedOffset;this.setListOffset(-listOffset)this.imageListOffset = listOffset;} else if ((calculatedOffset < -maxAllowedOffset) && (this.imageIndex < this.imageMaxLength - 1)) {// 左滑 -- 当前滑动超过最大值时 并且 不是最后一个元素去设置list偏移量显"下一张"图片let listOffset = calculatedOffset + maxAllowedOffset;this.setListOffset(Math.abs(listOffset))this.imageListOffset = listOffset;}
}

主轴处理的核心逻辑包括:

  1. 根据listDirection确定主轴方向(X或Y)
  2. 计算图片在主轴方向上的最大允许偏移量
  3. 处理边界情况,确保图片不会超出合理范围
  4. 当图片达到边缘时,显示下一张图片的预览

3. 交叉轴处理

交叉轴处理负责图片在非主滑动方向上的移动:

// 设置交叉轴位置
setCrossAxis(event: GestureEvent) {// list当前没有在移动 &&  交叉轴时候如果没有放大也不移动let isScale = this.imageScaleInfo.scaleValue !== this.imageScaleInfo.defaultScaleValue;let listOffset = Math.abs(this.imageListOffset);if (listOffset > this.moveMaxOffset) {this.isMoveCrossAxis = false;}if (this.isMoveCrossAxis && isScale) {// 获取交叉轴方向let direction: "X" | "Y" = this.listDirection === Axis.Horizontal ? "Y" : "X";// 获取交叉轴中对应的是 width 还是 heightlet imageWH = this.listDirection === Axis.Horizontal ? ImageFitType.TYPE_HEIGHT : ImageFitType.TYPE_WIDTH;// 获取手指在主轴移动偏移量let offset = event[`offset${direction}`];// 获取图片最后一次在主轴移动的数据let lastOffset = imageWH === ImageFitType.TYPE_WIDTH ? this.imageOffsetInfo.lastX : this.imageOffsetInfo.lastY;// 计算当前移动后偏移量结果let calculatedOffset = lastOffset + offset;// 设置交叉轴数据this.setCurrentOffsetXY(imageWH, calculatedOffset)}
}

交叉轴处理的核心逻辑包括:

  1. 只有当图片被放大时,才允许在交叉轴方向上移动
  2. 当图片处于切换预览状态时(imageListOffset > moveMaxOffset),禁止交叉轴移动
  3. 根据listDirection确定交叉轴方向(与主轴相反)

三、双指缩放手势实现

1. 手势定义

PinchGesture({ fingers: 2, distance: 1 }).onActionUpdate((event: GestureEvent) => {let scale = this.imageScaleInfo.lastValue * event.scale;// 限制缩放范围if (scale > this.imageScaleInfo.maxScaleValue * (1 + this.imageScaleInfo.extraScaleValue)) {scale = this.imageScaleInfo.maxScaleValue * (1 + this.imageScaleInfo.extraScaleValue);}if (scale < this.imageScaleInfo.defaultScaleValue * (1 - this.imageScaleInfo.extraScaleValue)) {scale = this.imageScaleInfo.defaultScaleValue * (1 - this.imageScaleInfo.extraScaleValue);}this.imageScaleInfo.scaleValue = scale;// 应用矩阵变换this.matrix = matrix4.identity().scale({x: this.imageScaleInfo.scaleValue,y: this.imageScaleInfo.scaleValue,}).rotate({x: 0,y: 0,z: 1,angle: this.imageRotateInfo.currentRotate,}).copy();}).onActionEnd((event: GestureEvent) => {// 当小于默认大小时,恢复为默认大小if (this.imageScaleInfo.scaleValue < this.imageScaleInfo.defaultScaleValue) {runWithAnimation(() => {this.imageScaleInfo.reset();this.imageOffsetInfo.reset();this.matrix = matrix4.identity().rotate({x: 0,y: 0,z: 1,angle: this.imageRotateInfo.currentRotate,}).copy();})}// 当大于最大缩放因子时,恢复到最大if (this.imageScaleInfo.scaleValue > this.imageScaleInfo.maxScaleValue) {runWithAnimation(() => {this.imageScaleInfo.scaleValue = this.imageScaleInfo.maxScaleValue;this.matrix = matrix4.identity().scale({x: this.imageScaleInfo.maxScaleValue,y: this.imageScaleInfo.maxScaleValue}).rotate({x: 0,y: 0,z: 1,angle: this.imageRotateInfo.currentRotate,});})}this.imageScaleInfo.stash();})

2. 缩放处理逻辑

双指缩放手势的核心逻辑包括:

  1. 根据event.scale计算新的缩放值,基于上次缩放的结果(lastValue)
  2. 限制缩放范围,防止过度放大或缩小
  3. 应用矩阵变换,实现图片的缩放效果
  4. 手势结束时,处理边界情况:
    • 如果缩放值小于默认值,恢复到默认大小
    • 如果缩放值大于最大值,恢复到最大值
  5. 保存当前缩放值为最后缩放值,用于下次缩放的基准计算

3. 弹性缩放体验

组件通过extraScaleValue属性提供了弹性缩放体验,允许用户在手势过程中临时超出缩放限制,但在手势结束时会恢复到合理范围:

// 允许的最大缩放范围
let maxScale = this.imageScaleInfo.maxScaleValue * (1 + this.imageScaleInfo.extraScaleValue);
// 允许的最小缩放范围
let minScale = this.imageScaleInfo.defaultScaleValue * (1 - this.imageScaleInfo.extraScaleValue);

四、总结

通过上述代码,实现了一个具有手势交互的图片预览组件,实现了图片的缩放、旋转、移动、切换预览等功能。通过使用手势库,实现了对图片的交互,并实现了各种手势的响应。


http://www.ppmy.cn/devtools/167255.html

相关文章

人工智能之数学基础:线性变换的象空间和零空间

本文重点 前面的课程中,我们学习了线性变换,由此而引申出线性变换的象空间和零空间,这两个空间在机器学习领域会被经常用到,本文对此进行学习。 直观理解 总的来说象空间就是经过线性变换得到的空间,零空间就是经过线性变换是零的元素构成的空间。 从几何角度来看,象空…

Linux第三次作业

一.创建根目录结构中的所有的普通文件 使用 mkdir -pv [路径] 创建目录文件 使用 touch [路径] 创建普通文件 二.列出所有账号的账号名 用 cat 命令查看在 /etc/passwd 中的用户信息 用 cut -d [ ] -f[ ] 命令切割出所有用户名 三.将/etc/passwd中内容按照冒号隔开的第三个字符…

信创系统安全优化与持续改进策略有哪些?

信创系统&#xff08;信息技术应用创新系统&#xff09;的安全优化与持续改进是保障国产化技术生态安全可靠运行的关键。以下从技术、管理、组织等多个维度提出系统性策略&#xff0c;并结合实际场景展开说明&#xff1a; 一、技术层面的安全优化策略 1. 核心组件安全加固 …

Web网页制作(静态网页):千年之恋

一、是用的PyCharm来写的代码 二、代码中所用到的知识点&#xff08;无 js&#xff09; 这段HTML代码展示了一个简单的注册页面&#xff0c;包含了多个HTML元素和CSS样式的应用。 这段HTML代码展示了一个典型的注册页面&#xff0c;包含了常见的HTML元素和表单控件。通过CSS样…

【调研】olmOCR解析PDF

测试用例&#xff1a; olmOCR GOT-OCR 将最底下没有文字的部分&#xff0c;可能是样式解析出重复 olmOCR GOT-OCR 无重复 重复 速度上&#xff0c;olmOCR效果更快 效果上&#xff0c;olmOCR解析得到的内容排版更加清晰整齐&#xff0c;而且对于6份GOT-OCR有重复的测…

Axure设计之堆叠柱状图教程(中继器)

堆叠柱状图是一种常用的数据可视化工具&#xff0c;它通过在同一柱状图内堆叠不同类别的数据&#xff0c;以展示每个类别在总体中的贡献或占比。堆叠柱状图不仅可以帮助我们观察数据的总量&#xff0c;还能清晰地揭示各部分之间的关系和变化趋势。以下是一个使用Axure制作动态效…

微信小程序校园跑腿的设计与实现【lw+源码+部署+视频+讲解】

第一章 绪论 1.1 本课题研究背景 近年来城市与社会经济发展较快&#xff0c;人们的生活水平不断提高&#xff0c;消费观念发生很大变化&#xff0c;随着 微信小程序技术的发展&#xff0c;小程序已经渗透到人们日常生活的方方面面&#xff0c;悄悄地改变着人们的生活方式。在…

GD32F4xx系列单片机-串口配合DMA的使用

将初始化、DMA 配置和数据发送/接收部分分成三个函数。 代码&#xff1a; 1. 初始化函数&#xff08;UartxInit&#xff09; 该函数用于初始化串口相关硬件设置&#xff08;GPIO、USART 和 DMA&#xff09;。 void UartxInit(uarttypedefenum com){ /* 使能GPIO时钟 */ …