Advanced Interaction Patterns with Composite Gestures in ArkUI

ArkUI facilitates sophisticated user inputs by allowing developers to chain or merge basic touch primitives into composite actions. The primary mechanism is the GestureGroup constructor, which evaluates multiple gesture nodes simultaneously or sequentially based on a specified mode.

GestureGroup(mode: GestureMode, ...gestureNodes: GestureNode[])
Parameter Description
mode Required. A GestureMode enumeration value that determines the execution flow of the wrapped gestures.
gesture Required. An iterable set of primitive gestures that form the composite behavior.

Sequential Execution Flow

This mode enforces a strict temporal order. Each gesture in the array must successfully identify before the subsequent gesture becomes active. If the initial condition fails, the entire chain resets. This is ideal for multi-step commands.

// SequentialExample.ets
@Component
struct SequentialGestureDemo {
  @State stepCount: number = 0
  @State boxColor: string = '#CCCCCC'
  @State dragOffset: Offset = { x: 0, y: 0 }

  build() {
    Column().height('100%').width('100%') {
      Row() {
        Text(`Phase ${this.stepCount}`)
          .fontColor('#333333')
          .fontSize(20)
        Text(` Dragged? ${this.dragOffset.x > 0 || this.dragOffset.y > 0 ? 'Yes' : 'No'}`)
          .fontColor('#666666')
      }
      .borderRadius(8)
      .padding(12)

      Circle({ width: 150, height: 150 })
        .fill(this.boxColor)
        .strokeDashArray([10, 5])
        .translate(this.dragOffset)
    }
    .gesture(
      GestureGroup(GestureMode.Sequence,
        LongPressGesture({ duration: 800 }).onAction(() => {
          this.stepCount = 1
          this.boxColor = '#FF5722'
          console.info('Sequence: Press registered')
        }),
        PanGesture().onActionUpdate((event: GestureEvent) => {
          if (this.stepCount === 1) {
            this.dragOffset = {
              x: this.dragOffset.x + event.offsetX,
              y: this.dragOffset.y + event.offsetY
            }
            console.info('Sequence: Dragging initiated')
          }
        }).onActionEnd(() => {
          this.boxColor = '#AAAAAA'
          this.stepCount = 0
        })
      )
    )
  }
}

Concurrent Detection

Parallel evaluation allows independent gesture branches to operate simultaneously. The runtime monitors touch streams and dispatches events to matching branches without requiring sequential completion. This pattern suits features like independent button states or simultaneous scaling.

// ParallelExample.ets
@Component
struct ParallelGestureDemo {
  @State singleTapCount: number = 0
  @State doubleTapCount: number = 0
  @State ringProgress: number = 0

  build() {
    Column().alignItems(HorizontalAlign.Center).margin(20) {
      Progress({ value: this.ringProgress, total: 100, type: ProgressType.Circular })
        .width(120)
        .height(120)
        .backgroundColor('#F0F0F0')
        .strokeWidth(8)

      Text(`Single: ${this.singleTapCount} | Double: ${this.doubleTapCount}`)
        .marginTop(20)
        .fontSize(14)
    }
    .gesture(
      GestureGroup(GestureMode.Parallel,
        TapGesture({ count: 1 }).onAction(() => {
          this.singleTapCount++
          this.ringProgress = Math.min(this.ringProgress + 10, 100)
        }),
        TapGesture({ count: 2 }).onAction(() => {
          this.doubleTapCount++
          this.ringProgress = Math.max(this.ringProgress - 20, 0)
        })
      )
    )
  }
}

Mutual Exclusion Handling

Exclusive mode resolves conflicts when overlapping touch patterns could trigger multiple behaviors. When simultaneous input is detected, the system prioritizes a single gesture branch, ignoring the others until the primary interaction concludes. This prevants jitter and unintended state changes during ambiguous touches.

// ExclusiveExample.ets
@Component
struct ExclusiveGestureDemo {
  @State scaleValue: number = 1.0
  @State translatePos: Offset = { x: 0, y: 0 }
  @State activeMode: string = 'None'

  build() {
    Column().height('100%').width('100%').justifyContent(FlexAlign.Center) {
      Rect()
        .width(120)
        .height(120)
        .fill('#007BFF')
        .scale({ x: this.scaleValue, y: this.scaleValue })
        .translate(this.translatePos)

      Text(`Current Operation: ${this.activeMode}`)
        .fontColor('#FFFFFF')
        .padding(10)
    }
    .clip(true)
    .background(Color.Gray)
    .gesture(
      GestureGroup(GestureMode.Exclusive,
        PinchGesture().onAction((event: GestureEvent) => {
          this.activeMode = 'Pinch Zoom'
          this.scaleValue += event.distanceX * 0.001
          this.scaleValue = Math.min(Math.max(this.scaleValue, 0.5), 3.0)
        }).onActionEnd(() => {
          this.activeMode = 'None'
        }),
        PanGesture().onActionUpdate((event: GestureEvent) => {
          this.activeMode = 'Pan Move'
          this.translatePos = {
            x: this.translatePos.x + event.offsetX,
            y: this.translatePos.y + event.offsetY
          }
        }).onActionEnd(() => {
          this.activeMode = 'None'
        })
      )
    )
  }
}

Composite gesture handling provides precise control over complex interaction models. Selecting the appropriate GestureMode ensures predictable feedback loops and reduces collision artifacts during multi-touch scenarios.

Tags: ArkUI HarmonyOS GestureGroup Touch Events Mobile UI Development

Posted on Mon, 18 May 2026 06:03:59 +0000 by dark_destroyer