HarmonyOS 鸿蒙Next中图片预览效果
HarmonyOS 鸿蒙Next中图片预览效果 怎么实现类似聊天消息图片视频预览功能,图片可以缩放,拖动等,长按图片或者视频可以保存本地相册功能,视频禁止缩放。
【解决方案】
- 监听到双击手势后,若当前图片处于放大状态,则将图片恢复至原大小;若当前图片未缩放,则将其放大至设定的两倍。
TapGesture({ count: StyleConstants.TAPGESTURE_FINGERS_COUNT })
.onAction(() => {
if (this.activeImage.scale > 1) {
this.activeImage.scale = 1
this.activeImage.offsetX = 0
this.activeImage.offsetY = 0
this.activeImage.offsetStartX = 0
this.activeImage.offsetStartY = 0
this.disabledSwipe = false
} else {
this.activeImage.scale = 2
this.disabledSwipe = true
}
})
- 在onActionStart中获取初始缩放比例值,在onActionUpdate中通过scale获取相对比例变化量并计算目标缩放比。在双指捏合缩放的过程中,通过调整图片的offsetX和offsetY保障视觉中心稳定。
PinchGesture({ fingers: StyleConstants.PINCHGESTURE_FINGERS_COUNT })
.onActionStart((event) => {
this.defaultScale = this.activeImage.scale
})
.onActionUpdate((event) => {
let scale = event.scale * this.defaultScale
if (scale <= 4 && scale >= 1) {
this.activeImage.offsetX = this.activeImage.offsetX / (this.activeImage.scale - 1) * (scale - 1) || 0
this.activeImage.offsetY = this.activeImage.offsetY / (this.activeImage.scale - 1) * (scale - 1) || 0
this.activeImage.scale = scale
}
this.disabledSwipe = this.activeImage.scale > 1
})
- 在onActionStart中获取初始触点的坐标信息,在onActionUpdate中根据当前触点坐标、初始触点坐标以及初始坐标偏移计算出当前的坐标偏移。在滑动过程中计算图片的最大可移动范围作为边界约束,当图片的坐标偏移在最大可移动范围内时,可实现图片的滑动。
PanGesture()
.onActionStart(event => {
this.activeImage.dragOffsetX = event.fingerList[0].globalX
this.activeImage.dragOffsetY = event.fingerList[0].globalY
})
.onActionUpdate((event) => {
if (this.activeImage.scale === 1) {
return
}
let offsetX = event.fingerList[0].globalX - this.activeImage.dragOffsetX +this.activeImage.offsetStartX
let offsetY = event.fingerList[0].globalY - this.activeImage.dragOffsetY +this.activeImage.offsetStartY
if (this.activeImage.width * this.activeImage.scale > this.containerWidth &&
(this.activeImage.width * this.activeImage.scale - this.containerWidth) / 2 >=
Math.abs(offsetX)) {
this.activeImage.offsetX = offsetX
}
if (this.activeImage.height * this.activeImage.scale >
this.containerHeight &&
(this.activeImage.height * this.activeImage.scale - this.containerHeight) / 2 >=
Math.abs(offsetY)) {
this.activeImage.offsetY = offsetY
}
})
更多关于HarmonyOS 鸿蒙Next中图片预览效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
图片/视频容器搭建:
使用 Swiper + Grid 组合实现多图切换与布局:
Swiper() {
ForEach(this.mediaList, (item: MediaItem) => {
// 图片类型处理
if (item.type === 'image') {
Image(item.uri)
.gesture(this.buildImageGesture()) // 绑定手势
} else {
Video({ src: item.uri })
.controls(false) // 视频禁止缩放
}
})
}
通过组合手势实现缩放与拖动:
private buildImageGesture(): GestureGroup {
const panGesture = new PanGesture({ fingers: 1 })
.onActionUpdate((event: GestureEvent) => {
// 拖动偏移量处理
this.offsetX = event.offsetX
this.offsetY = event.offsetY
})
const pinchGesture = new PinchGesture()
.onActionUpdate((event: PinchGestureEvent) => {
// 双指缩放计算
this.scale = event.scale * this.lastScale
this.scale = Math.min(Math.max(this.scale, 0.5), 3) // 限制缩放范围
})
return GestureGroup.parallel(panGesture, pinchGesture)
}
长按保存功能
长按事件监听
Image(item.uri)
.onLongPress(() => {
this.showSaveDialog(item.uri) // 弹出保存选项
})
保存到相册实现
private async saveToAlbum(uri: string) {
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.context)
const photoAsset = await phAccessHelper.createAsset(uri)
phAccessHelper.addAssetsToAlbum([photoAsset], albumId) // 需获取相册ID
}
视频处理
禁用缩放逻辑:通过条件判断区分媒体类型
Video({ src: item.uri })
.gesture(
item.type === 'video' ?
GestureGroup.parallel() : // 空手势组
this.buildImageGesture()
)
视频播放控制:添加播放/暂停按钮覆盖层
Stack() {
Video({ src: item.uri })
Button()
.onClick(() => this.videoController.start())
}
楼主的需求和这个示例很像 可以参考一下:图片预览器-布局与弹窗 - 华为HarmonyOS开发者
图片预览器是常见的开发应用场景
图片拖拽放大缩小
@Entry
@Component
struct Page2 {
@State scaleValue: number = 1;
@State pinchValue: number = 1;
@State count: number = 0;
@State offsetX: number = 0;
@State offsetY: number = 0;
@State positionX: number = 0;
@State positionY: number = 0;
@State boxWidth: number = 200;
@State boxHeight: number = 200;
maxW:number = 0
minW:number = 0
maxH:number = 0
minH:number = 0
dwidth:number= 0
dheight:number= 0
fWidth:number = 0
fHeight:number = 0
x:number = 0
y:number = 0
build() {
Column() {
Column() {
Text("1231231231223132112321")
Text("1231231231223132112321")
Text("1231231231223132112321")
Text("1231231231223132112321")
Text("1231231231223132112321")
Column()
.width(20)
.aspectRatio(1)
.backgroundColor(Color.Red)
.scale({ x: 1/ this.scaleValue, y: 1/this.scaleValue, z: 1 })
.position({
right: -10,
bottom: -10
})
.gesture(
PanGesture()
.onActionUpdate((event?: GestureEvent) => {
if (event) {
//旧的大小
let oldArea = this.pinchValue * this.boxWidth
//计算在旧的大小上放大了多少倍
this.scaleValue = (oldArea + event.offsetX) / oldArea
//旧的放大倍数乘以新的放大倍数得到全新的放大倍数
this.scaleValue *= this.pinchValue
//重新计算位置偏移
//1.计算this.scaleValue * this.boxWidth 得到新的宽
//2.计算this.pinchValue * this.boxWidth 得到旧的宽
//3. (this.scaleValue * this.boxWidth - this.pinchValue * this.boxWidth) / 2 得到偏移量,这样才能保证左上角位置不变
this.offsetX =
this.positionX + (this.scaleValue * this.boxWidth - this.pinchValue * this.boxWidth) / 2;
this.offsetY =
this.positionY + (this.scaleValue * this.boxWidth - this.pinchValue * this.boxWidth) / 2;
this.changeCheckBoundaryValue();
}
})
.onActionEnd(() => {
this.pinchValue = this.scaleValue
this.positionX = this.offsetX;
this.positionY = this.offsetY;
})
)
}
.width(this.boxWidth)
.height(this.boxHeight)
.border({
width: 1,
color: Color.Black
})
.onAreaChange((oldValue: Area, newValue: Area)=>{
this.dwidth = newValue.width as number
this.dheight = newValue.height as number
this.x = newValue.position.x as number
this.y = newValue.position.y as number
this.minW = -this.x
this.maxW = this.fWidth as number - this.dwidth - this.x
this.minH = -this.y
this.maxH = this.fHeight as number - this.dheight - this.y
})
.draggable(false)
.scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.gesture(
GestureGroup(GestureMode.Parallel,
PinchGesture({ fingers: 2 })
.onActionUpdate((event?: GestureEvent) => {
if (event) {
this.scaleValue = this.pinchValue * event.scale;
this.changeCheckBoundaryValue();
}
})
.onActionEnd(() => {
this.pinchValue = this.scaleValue;
}),
PanGesture()
.onActionUpdate((event?: GestureEvent) => {
if (event) {
console.log("xxxx:",this.scaleValue)
this.offsetX = this.positionX + event.offsetX * this.scaleValue;
this.offsetY = this.positionY + event.offsetY * this.scaleValue;
this.checkBoundaryValue()
}
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.pinchValue = this.scaleValue
})
)
)
}
.onSizeChange((oldValue: SizeOptions, newValue: SizeOptions)=>{
//父组件容器宽高
this.fWidth = newValue.width as number
this.fHeight = newValue.height as number
})
.margin({top:40,left:20})
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width(310)
.height(320)
.backgroundColor("#29000000")
}
/**
* 重新计算边界值
*/
changeCheckBoundaryValue() {
this.minW = -this.x + ((this.scaleValue - 1) * this.dwidth / 2)
this.maxW = this.fWidth as number - this.dwidth * this.scaleValue - (this.x - (this.scaleValue - 1) * this.dwidth / 2);
this.minH = -this.y + ((this.scaleValue - 1) * this.dheight / 2)
this.maxH = this.fHeight as number - this.dheight* this.scaleValue - (this.y - (this.scaleValue - 1) * this.dheight / 2);
}
//处理边界值
checkBoundaryValue() {
if (this.offsetY > this.maxH) {
this.offsetY = this.maxH
}
if (this.offsetY < this.minH) {
this.offsetY = this.minH
}
if (this.offsetX > this.maxW) {
this.offsetX = this.maxW
}
if (this.offsetX < this.minW) {
this.offsetX = this.minW
}
//拖动到右下角边缘
if( this.offsetX === this.maxW && this.offsetY === this.maxH){
//缩小0.2倍
this.scaleValue = this.pinchValue * 0.2
//重新计算边界值
this.changeCheckBoundaryValue()
this.positionX = this.maxW;
this.positionY = this.maxH;
}
}
}
推荐这个插件https://ohpm.openharmony.cn/#/cn/detail/@rv%2Fimage-preview
没有默认的,自己实现吧,也不复杂
鸿蒙Next的图片预览功能基于ArkUI框架实现,支持常见图片格式(JPG、PNG、WEBP等)。通过Image组件可实现基础预览,结合Gallery组件可支持多图滑动浏览、缩放及手势操作。系统提供PixelMap进行底层图片数据处理,支持EXIF信息读取。预览性能通过异步加载和内存优化保障流畅体验。
在HarmonyOS Next中,可以通过以下方式实现图片和视频预览功能:
-
使用
ImagePreview
组件实现图片预览,支持缩放和拖动操作。通过设置enableZoom
和enableDrag
属性控制交互行为。 -
视频预览使用
VideoPlayer
组件,通过设置scalingMode
为ScalingMode.FIT
禁止缩放,保持原始比例播放。 -
长按保存功能可通过
onLongPress
事件监听实现,调用MediaLibrary
接口将文件保存至相册。注意处理运行时权限申请(READ_MEDIA和WRITE_MEDIA)。
示例代码片段:
// 图片预览
ImagePreview.show({
images: [imageUrl],
enableZoom: true,
enableDrag: true
})
// 长按保存
onLongPress(() => {
const mediaLib = mediaLibrary.getMediaLibrary(context)
mediaLib.createAsset(mediaLibrary.MediaType.IMAGE, 'image.jpg', (err, asset) => {
// 保存逻辑
})
})
视频组件需单独处理交互限制,避免缩放操作。