温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦!
手势处理是图片预览组件的核心交互功能,通过识别和响应用户的各种触摸操作,实现图片的缩放、旋转、拖动和切换等功能。本文将详细介绍PicturePreviewImage组件中的手势处理实现原理。
图片预览组件支持以下几种手势类型:
手势类型 | 触发方式 | 功能 |
---|---|---|
单指拖动 | 单指在屏幕上滑动 | 移动图片、触发图片切换 |
双指缩放 | 两指捏合或分开 | 放大或缩小图片 |
双指旋转 | 两指旋转 | 旋转图片 |
双击 | 快速点击两次 | 在默认大小和适配屏幕大小之间切换 |
图片预览组件采用了组合手势的处理架构,通过GestureGroup将多种手势组合在一起,实现复杂的交互效果:
// 单指手势组
GestureGroup(
GestureMode.Parallel,
TapGesture({ count: 2 }), // 双击手势
PanGesture({ fingers: 1 }) // 单指拖动手势
)
// 双指手势组
GestureGroup(
GestureMode.Parallel,
RotationGesture({ angle: this.imageRotateInfo.startAngle }), // 旋转手势
PinchGesture({ fingers: 2, distance: 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();
})
主轴处理是单指拖动手势的核心,它负责处理图片在主滑动方向上的移动:
// 设置主轴位置
setPrincipalAxis(event: GestureEvent) {
// 获取主轴方向
let direction: "X" | "Y" = this.listDirection === Axis.Horizontal ? "X" : "Y";
// 获取主轴中对应的是 width 还是 height
let 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;
}
}
主轴处理的核心逻辑包括:
交叉轴处理负责图片在非主滑动方向上的移动:
// 设置交叉轴位置
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 还是 height
let 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)
}
}
交叉轴处理的核心逻辑包括:
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();
})
双指缩放手势的核心逻辑包括:
组件通过extraScaleValue属性提供了弹性缩放体验,允许用户在手势过程中临时超出缩放限制,但在手势结束时会恢复到合理范围:
// 允许的最大缩放范围
let maxScale = this.imageScaleInfo.maxScaleValue * (1 + this.imageScaleInfo.extraScaleValue);
// 允许的最小缩放范围
let minScale = this.imageScaleInfo.defaultScaleValue * (1 - this.imageScaleInfo.extraScaleValue);
通过上述代码,实现了一个具有手势交互的图片预览组件,实现了图片的缩放、旋转、移动、切换预览等功能。通过使用手势库,实现了对图片的交互,并实现了各种手势的响应。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。