ArkUI 手势冲突:别等用户滑不动了才处理

举报
蓝瘦的蜕变 发表于 2026/10/23 15:07:21 2026/10/23
【摘要】 今天我们看手势冲突里最基础的裁决入口。 很多手势问题不是“代码不生效”,而是两个动作都觉得自己应该生效。比如横向拖动卡片、纵向滚动列表、子组件点击,这些意图如果不拆开,用户一滑不动就会很烦。 参考文档: https://developer.huawei.com/consumer/cn/doc/harmonyosguid

ArkUI 手势冲突:别等用户滑不动了才处理

今天我们看手势冲突里最基础的裁决入口。

很多手势问题不是“代码不生效”,而是两个动作都觉得自己应该生效。比如横向拖动卡片、纵向滚动列表、子组件点击,这些意图如果不拆开,用户一滑不动就会很烦。

参考文档:

  • https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-interaction-development-guide-overview

用裁决继续或拒绝

.onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent): GestureJudgeResult => {
  return this.rejectPan ? GestureJudgeResult.REJECT : GestureJudgeResult.CONTINUE
})

CONTINUE 表示继续交给系统识别,REJECT 表示当前组件的这个手势不要继续。Demo 用开关模拟这件事,方便你直接看到差别。

这里我们还会在 onActionUpdate 里补一层兜底:拒绝状态下不再写入 panX。原因很现实,裁决回调和手势更新回调在不同设备、不同识别阶段可能不是你想象中的“先后永远固定”。真正写位移的地方也守住,Demo 才不会出现“显示拒绝了,但卡片还在动”的错觉。

识别器裁决只做观察

.onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, recognizers: Array<GestureRecognizer>): GestureJudgeResult => {
  this.recognizerText = `识别器数量 ${recognizers.length}`
  return GestureJudgeResult.CONTINUE
})

这一篇只做轻量观察,不做复杂业务。真要处理列表嵌套拖拽,先把“谁应该赢”定清楚,再写裁决逻辑。

你有没有遇到过横滑卡片和纵向列表互相抢手势?如果有,下一步就很适合把这个例子扩成列表场景。

完整示例代码

文件位置:entry/src/main/ets/pages/arkUI/interaction/ArkUIGestureConflictDemo.ets

@Entry
@Component
struct ArkUIGestureConflictDemo {
  @State rejectPan: boolean = false
  @State judgeText: string = '拖动卡片,先看默认识别流程'
  @State recognizerText: string = '识别器裁决还没有触发'
  @State parallelText: string = '内置识别器并行判断还没有触发'
  @State panX: number = 0

  private showTip(message: string): void {
    this.getUIContext().getPromptAction().showToast({
      message: message,
      duration: 1200,
      bottom: 120
    })
  }

  @Builder
  sectionTitle(title: string, desc: string) {
    Column({ space: 6 }) {
      Text(title)
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text(desc)
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  build() {
    Scroll() {
      Column({ space: 22 }) {
        Column({ space: 8 }) {
          Text('ArkUI 手势冲突')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#111827')
          Text('别等用户滑不动了才想起来处理')
            .fontSize(16)
            .fontColor('#4B5563')
          Text('这一页只做轻量裁决演示:什么时候继续识别,什么时候拒绝当前手势。真正复杂的列表嵌套和业务规则,应该先把意图拆清楚。')
            .fontSize(14)
            .fontColor('#6B7280')
            .lineHeight(22)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)

        this.sectionTitle('先切换裁决结果', '打开开关后,onGestureJudgeBegin 会拒绝当前拖动手势;关闭后继续识别。')
        Toggle({ type: ToggleType.Switch, isOn: this.rejectPan })
          .onChange((isOn: boolean) => {
            this.rejectPan = isOn
            this.judgeText = isOn ? '当前策略:拒绝拖动手势' : '当前策略:允许拖动手势'
            if (isOn) {
              this.panX = 0
            }
            this.showTip(isOn ? '将拒绝拖动' : '允许拖动')
          })

        Column({ space: 12 }) {
          Text('可拖动卡片')
            .fontSize(19)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          Text('横向拖动试试。裁决拒绝时,拖动反馈不会继续推进。')
            .fontSize(13)
            .fontColor('#DBEAFE')
            .lineHeight(20)
        }
        .width('100%')
        .padding(18)
        .borderRadius(18)
        .backgroundColor('#2563EB')
        .translate({ x: this.panX, y: 0 })
        .gesture(
          PanGesture({ fingers: 1, direction: PanDirection.Horizontal, distance: 5 })
            .tag('conflict-pan')
            .onActionUpdate((event: GestureEvent) => {
              if (this.rejectPan) {
                this.panX = 0
                this.judgeText = '拖动已被拒绝:卡片保持原位'
                return
              }
              this.panX = event.offsetX
              this.judgeText = `拖动中:x ${Math.round(event.offsetX)}`
            })
            .onActionEnd(() => {
              this.panX = 0
              this.judgeText = this.rejectPan ? '拖动已拒绝:没有产生位移结果' : '拖动结束:卡片回到原位'
              this.showTip(this.rejectPan ? '拖动已拒绝' : '拖动结束')
            })
        )
        .onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent): GestureJudgeResult => {
          this.judgeText = `onGestureJudgeBegin:${this.rejectPan ? 'REJECT' : 'CONTINUE'},type ${gestureInfo.type}`
          return this.rejectPan ? GestureJudgeResult.REJECT : GestureJudgeResult.CONTINUE
        })
        .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, recognizers: Array<GestureRecognizer>): GestureJudgeResult => {
          this.recognizerText = `onGestureRecognizerJudgeBegin:识别器数量 ${recognizers.length}`
          if (this.rejectPan && current.getTag() === 'conflict-pan') {
            current.preventBegin()
            this.recognizerText = 'onGestureRecognizerJudgeBegin:conflict-pan 已阻止 begin'
            return GestureJudgeResult.REJECT
          }
          return GestureJudgeResult.CONTINUE
        })
        .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>): GestureRecognizer => {
          this.parallelText = `shouldBuiltInRecognizerParallelWith:候选数量 ${others.length}`
          return current
        })

        Column({ space: 8 }) {
          Text(this.judgeText)
            .fontSize(14)
            .fontColor('#111827')
            .lineHeight(22)
          Text(this.recognizerText)
            .fontSize(14)
            .fontColor('#111827')
            .lineHeight(22)
          Text(this.parallelText)
            .fontSize(14)
            .fontColor('#111827')
            .lineHeight(22)
        }
        .width('100%')
        .padding(16)
        .borderRadius(14)
        .backgroundColor('#FFFFFF')
        .border({ width: 1, color: '#E5E7EB' })
      }
      .width('100%')
      .padding(24)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8FAFC')
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。