HarmonyOS 鸿蒙Next中有没有手势处理的框架

HarmonyOS 鸿蒙Next中有没有手势处理的框架 cke_332.gif

类似这样的效果,大佬们救救


更多关于HarmonyOS 鸿蒙Next中有没有手势处理的框架的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

开发者你好:

暂时并没有直接的实现希望效果的框架,可以参考下面的组合手势案例实现所希望的对图片平移、缩放、矩阵变换等操作。

【背景知识】

  • GestureGroup(组合手势):手势识别组合,即多种手势组合为复合手势,支持连续识别、并行识别和互斥识别。
  • 图形变换:该通用属性能用于对组件进行旋转、平移、缩放、矩阵变换等操作。
  • 位置设置:该通用属性能设置组件的对齐方式、布局方向和显示位置。

【解决方案】

  1. 创建图片预览页面,实现在点击图片时,进入图片预览页面,进行大图展示及移动缩放操作。
  2. 通过在图片预览页面设置组合手势GestureMode控制Image组件的scale属性从而控制图片缩放,以及控制offset/position属性从而控制Image组件的位置。

代码实现方式,参考如下:

  1. 创建评论浏览主页面。
import { ImagePreviewParam } from './ImagePreview';

@Entry
@Component
struct Index {
  @State imageList: (string | Resource)[] = [
    $r('app.media.startIcon'),
    $r('app.media.startIcon'),
    $r('app.media.startIcon')
  ]
  @Provide pathStack: NavPathStack = new NavPathStack()

  build() {
    Navigation(this.pathStack) {
      Column() {
        List() {
          ForEach(this.imageList, (item: string | Resource, index: number) => {
            ListItem() {
              Image(item)
                .width('100%')
                .aspectRatio(1)
                .onClick(() => {
                  this.pathStack.pushPath({
                    name: 'ImagePreview',
                    param: { imageList: this.imageList, active: index } as ImagePreviewParam
                  })
                })
            }
            .width('50%')
            .aspectRatio(1)
          })
        }
        .layoutWeight(1)
        .width('100%')
      }
      .padding({
        top: AppStorage.get<number>('topRectHeight') || 0
      })
    }
    .height('100%')
    .width('100%')
    .hideTitleBar(true)
  }
}
  1. 创建图片预览页面。 内部用到的接口:
export interface ImagePreviewParam {
  imageList: (string | Resource)[]
  active: number
}

export interface imageSize {
  width: number
  height: number
}

export interface ImageSizeType {
  width: number
  height: number
  scale: number
  offsetX: number
  offsetY: number
  offsetStartX: number
  offsetStartY: number
  dragOffsetX: number
  dragOffsetY: number
}

核心逻辑实现代码:

@Component
struct ImagePreview {
  private imageListSize: imageSize[] = []
  private defaultScale: number = 1
  @Consume pathStack: NavPathStack
  @Prop imageList: (string | Resource)[]
  @Prop active: number
  @State containerHeight: number = 0
  @State containerWidth: number = 0
  @State disabledSwipe: boolean = false
  @State activeImage: ImageSizeType = {
    width: 0,
    height: 0,
    scale: 1,
    offsetX: 0,
    offsetY: 0,
    offsetStartX: 0,
    offsetStartY: 0,
    dragOffsetX: 0,
    dragOffsetY: 0,
  }

  build() {
    NavDestination() {
      Swiper() {
        ForEach(this.imageList, (item: string, i: number) => {
          Image(item)
            .width(`calc(100% * ${this.activeImage.scale})`)
            .height(`calc(100% * ${this.activeImage.scale})`)
            .objectFit(ImageFit.Contain)
            .draggable(false)
            .scale(this.active === i ? { x: this.activeImage.scale, y: this.activeImage.scale } : null)
            .offset(this.active === i ? { x: this.activeImage.offsetX, y: this.activeImage.offsetY } : null)
            .onComplete(e => {
              if (e?.loadingStatus) {
                this.imageListSize[i] = {
                  width: px2vp(Number(e.contentWidth)),
                  height: px2vp(Number(e.contentHeight))
                }
                if (this.active === i) {
                  this.activeImage.width = this.imageListSize[i].width
                  this.activeImage.height = this.imageListSize[i].height
                }
              }
            })
        }, (item: string, i) => i.toString())
      }
      .width('100%')
      .height('100%')
      .index(this.active)
      .backgroundColor('#000')
      .parallelGesture(
        GestureGroup(GestureMode.Exclusive,
          PinchGesture({ fingers: 2 })
            .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
            })
            .onActionEnd((event) => {
              this.disabledSwipe = this.activeImage.scale > 1
            })
            .onActionCancel(() => {
              this.disabledSwipe = this.activeImage.scale > 1
            }),
          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
              }
              if ((this.activeImage.width * this.activeImage.scale - this.containerWidth) / 2 < Math.abs(offsetX)) {
                this.disabledSwipe = false
              }
            })
            .onActionEnd((event) => {
              this.activeImage.offsetStartX = this.activeImage.offsetX
              this.activeImage.offsetStartY = this.activeImage.offsetY
            })
            .onActionCancel(() => {
              this.activeImage.offsetStartX = this.activeImage.offsetX
              this.activeImage.offsetStartY = this.activeImage.offsetY
            }),
          TapGesture({ count: 2 }) // 双击手势
            .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
              }
            }),
          TapGesture({ count: 1 }) // 单击手势
            .onAction(() => {
              this.pathStack?.pop()
            }),
        )
      )
      .disableSwipe(this.disabledSwipe)
      .onAnimationEnd((index) => {
        if (index !== this.active) {
          this.activeImage = {
            width: 0,
            height: 0,
            scale: 1,
            offsetX: 0,
            offsetY: 0,
            offsetStartX: 0,
            offsetStartY: 0,
            dragOffsetX: 0,
            dragOffsetY: 0,
          }
        }
        this.active = index
        this.disabledSwipe = this.activeImage.scale > 1
        this.activeImage.width = this.imageListSize[index].width
        this.activeImage.height = this.imageListSize[index].height
      })
      .itemSpace(50)
      .onAreaChange((_, n) => {
        this.containerWidth = Number(n.width)
        this.containerHeight = Number(n.height)
      })
    }
    .hideTitleBar(true)
    .padding({
      top: AppStorage.get<number>('topRectHeight') || 0,
      bottom: AppStorage.get<number>('bottomRectHeight') || 0,
    })
  }
}

@Builder
export function ImagePreviewBuilder(_: string, params: ImagePreviewParam) {
  ImagePreview({ imageList: params.imageList, active: params.active })
}

更多关于HarmonyOS 鸿蒙Next中有没有手势处理的框架的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS Next提供了专门的手势处理框架。该框架支持多种手势识别,包括点击、长按、拖拽、缩放和旋转等。开发者可通过Gesture组件和相关API直接实现手势交互功能,无需依赖其他语言或外部库。手势事件处理基于ArkTS/TypeScript实现,与鸿蒙UI组件无缝集成。

HarmonyOS Next提供了完善的手势处理框架,支持多种手势识别和自定义手势开发。通过GesturePanGesturePinchGesture等组件可以实现拖拽、缩放、旋转等交互效果。开发者可以使用@ohos.gesture模块进行手势事件监听和处理,结合@ohos.arkui的UI组件实现流畅的交互体验。具体可参考官方文档中的手势处理章节。

回到顶部