温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦!
Harmonyos NEXT 图片预览组件之PicturePreviewImage实现原理
效果预览
一、组件概述
PicturePreviewImage
是图片预览组件的核心内层组件,负责单张图片的展示和交互处理。该组件实现了图片的缩放、旋转、拖动等丰富的交互功能,为用户提供流畅的图片预览体验。
1. 组件定义
@Reusable
@Component
export struct PicturePreviewImage {
// 当前背景色
@Link listBGColor: Color;
// 图片显示的地址
@Require @Prop imageUrl: string = '';
// 图片滑动方向
@Require @Prop listDirection: Axis;
// 图片滑动多大距离需要切换图片
@Prop TogglePercent: number = 0.2;
// 当前图片下标
@Prop imageIndex: number = 0;
// 最多几张图片
@Prop imageMaxLength: number = 0;
// 设置偏移尺寸
setListOffset: (offset: number, animationDuration?: number) => void;
// 切换图片
setListToIndex: (index: number) => void;
// ...
}
2. 核心状态管理
组件使用多个状态模型来管理图片的不同交互状态:
// 图片旋转信息
@State imageRotateInfo: RotateModel = new RotateModel();
// 图片缩放信息
@State imageScaleInfo: ScaleModel = new ScaleModel(1.0, 1.0, 1.5, 0.3);
// 图片默认大小 -- 是转化后的大小
@State imageDefaultSize: image.Size = { width: 0, height: 0 };
// 表示当前图片是根据宽度适配还是高度适配
@State imageWH: ImageFitType = ImageFitType.TYPE_DEFAULT;
// 本模块提供矩阵变换功能,可对图形进行平移、旋转和缩放等
@State matrix: matrix4.Matrix4Transit = matrix4.identity().copy();
// 图片偏移信息
@State imageOffsetInfo: OffsetModel = new OffsetModel(0, 0);
二、核心功能实现
1. 图片尺寸计算
组件会根据图片原始宽高比和窗口大小,计算图片的默认显示尺寸:
calcImageDefaultSize(imageWHRatio: number, windowSize: window.Size): image.Size {
let width = 0
let height = 0;
if (imageWHRatio > windowSize.width / windowSize.height) {
// 图片宽高比大于屏幕宽高比,图片默认以屏幕宽度进行显示
width = windowSize.width;
height = windowSize.width / imageWHRatio;
} else {
height = windowSize.height;
width = windowSize.height * imageWHRatio;
}
return { width: width, height: height };
}
这段代码根据图片和屏幕的宽高比,决定图片是以屏幕宽度还是高度为基准进行适配显示。
2. 图片信息初始化
当图片加载完成后,组件会初始化图片的相关信息:
initCurrentImageInfo(event: ImageLoadResult): void {
let imageW = event.width;
let imageH = event.height;
let windowSize = windowSizeManager.get();
// 图片宽高比
this.imageWHRatio = imageW / imageH;
// 图片默认大小
this.imageDefaultSize = this.calcImageDefaultSize(this.imageWHRatio, windowSize);
// 图片宽度 等于 视口宽度 则图片使用宽度适配 否则 使用 高度适配
if (this.imageDefaultSize.width === windowSize.width) {
this.imageWH = ImageFitType.TYPE_WIDTH;
} else {
this.imageWH = ImageFitType.TYPE_HEIGHT;
}
// 设置最大缩放值
this.imageScaleInfo.maxScaleValue += this.imageWH === ImageFitType.TYPE_WIDTH ?
(windowSize.height / this.imageDefaultSize.height) :
(windowSize.width / this.imageDefaultSize.width);
}
这段代码完成了图片适配类型的判断和最大缩放值的计算,为后续的交互操作做准备。
3. 矩阵变换实现
组件使用matrix4矩阵变换实现图片的缩放和旋转效果:
// 缩放矩阵示例
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();
矩阵变换是实现图片缩放和旋转的核心技术,通过matrix4.identity()创建单位矩阵,然后应用缩放和旋转变换。
三、手势处理实现
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. 双指缩放
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();
})
双指缩放手势用于放大缩小图片,通过event.scale获取缩放比例,并应用到矩阵变换中。
3. 双指旋转
RotationGesture({ angle: this.imageRotateInfo.startAngle })
.onActionUpdate((event: GestureEvent) => {
let angle = this.imageRotateInfo.lastRotate + event.angle
if (event.angle > 0) {
angle -= this.imageRotateInfo.startAngle;
} else {
angle += this.imageRotateInfo.startAngle;
}
this.matrix = matrix4.identity()
.scale({
x: this.imageScaleInfo.scaleValue,
y: this.imageScaleInfo.scaleValue
})
.rotate({
x: 0,
y: 0,
z: 1,
angle: angle,
}).copy();
this.imageRotateInfo.currentRotate = angle;
})
双指旋转手势用于旋转图片,通过event.angle获取旋转角度,并应用到矩阵变换中。
4. 双击缩放
TapGesture({ count: 2 })
.onAction(() => {
let fn: Function;
// 当前大小倍数 大于 默认的倍数,则是放大状态需要缩小
if (this.imageScaleInfo.scaleValue > this.imageScaleInfo.defaultScaleValue) {
fn = () => {
// 恢复默认大小
this.imageScaleInfo.reset();
// 重置偏移量
this.imageOffsetInfo.reset();
// 设置一个新的矩阵
this.matrix = matrix4.identity().copy().rotate({
z: 1,
angle: this.imageRotateInfo.lastRotate
});
}
} else {
fn = () => {
// 这里是正常状态 -- 需要放大
// 获取放大倍数
const ratio: number = this.calcFitScaleRatio(this.imageDefaultSize, windowSizeManager.get());
// 设置当前放大倍数
this.imageScaleInfo.scaleValue = ratio;
// 重置偏移量
this.imageOffsetInfo.reset();
// 设置矩阵元素
this.matrix = matrix4.identity().scale({
x: ratio,
y: ratio,
}).rotate({
z: 1,
angle: this.imageRotateInfo.lastRotate
}).copy();
// 设置最后放大倍数设置为当前的倍数
this.imageScaleInfo.stash();
}
}
runWithAnimation(fn);
})
双击缩放功能实现了图片在默认大小和适配屏幕大小之间的切换,提供了便捷的缩放操作。
## 四、总结
本文介绍了图片浏览器的核心功能,包括图片的加载、缩放、旋转、拖动、双指缩放、双指旋转、双击缩放等。通过这些功能,我们可以实现一个功能完善的图片浏览器。同时,本文也提供了详细的代码实现,包括核心逻辑、事件处理、动画效果等。通过阅读本文,读者可以更好地理解和掌握图片浏览器的实现原理和技巧。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。