# Gesture System

In business development, we often need to listen to the nodetouchevent, and handle the drag-and-zoom logic.BecauseSkylineuses a dual-threaded architecture, there is a large asynchronous latency when performing such interactive animations, which can be referenced to wxs responding to events .

Skyline``wxsCode runs in theJSthread, and events are generated in theUIThreads, sowxs animationperformance is reduced. To improve the effect of the Weixin Mini Program interactive experience, we built a number of gesture components. The advantages of using gesture components include

  1. Eliminating the need for developers to listen totouchevents and compute the complex steps of gesture logic on their own
  2. The gesture component responds directly at theUIthread, avoiding delays caused by passing to theJSthread

# Effects display

The following diagram demonstrates the effects of dragging a ball using gestures, negotiating gestures, half-screen pop-up gestures to drag and close, and segmented half-screen. Click for more Skyline examples .

Scanning Weixin Mini Program examples to experience thebasic gesturesand thenegotiating gesturesnew features

# Gesture Component

Component Name Trigger timing
`` Triggered when you click
`` Triggered when double-clicking
`` Multiple indicators trigger when zooming
`` The iPhone device is triggered again on time
`` Triggered when you drag (horizontal / vertical)
`` Triggered when sliding vertically
`` Triggered when swiping laterally
`` Long time triggers

# Working principle

A gesture component is a virtual component , and its immediate child nodes are the ones that actually respond to events.In the code below, we add two types of gesture listening to thecontainernode.

  1. When swiping laterally across the screen, thehorizontal-draggesture node's callback will be triggered;
  2. A callback of thevertical-draggesture node is triggered when swiping lengthwise across the screen.
<horizontal-drag-gesture-handler>
  <vertical-drag-gesture-handler>
     <view id="container"></view>
  </vertical-drag-gesture-handler>
</horizontal-drag-gesture-handler>

When you touch the screen, the rendering engine recognizes gestures from the inside out, and when one of the listeners meets the criteria, the rest of the listeners fail.If you add a vertical gesture listener insidescroll-view, the gesture listeners insidescrollvieware blocked, causing the gestures to not swipe.

<scrol-view>
  <vertical-drag-gesture-handler>
     <view id="container"></view>
  </vertical-drag-gesture-handler>
</scroll-view>

It should be noted that thepantype is better than thevertical-dragis loose, so when sliding vertically,vertical-dragwill respond, whilepanwill fail.When swiping sideways, thepantype responds.

<vertical-drag-gesture-handler>
  <pan-gesture-handler>
     <view id="container"></view>
  </pan-gesture-handler>
</vertical-drag-gesture-handler>

# Common attributes

attribute type Default values Required to fill in Introductions
tag string nothing no Declaration of component identification during gesture consultation
worklet:ongesture eventhandler nothing no Gesture processing callbacks
worklet:should-response-on-move callback nothing no Does the gesture respond when the finger moves?
worklet:should-accept-gesture callback nothing no Whether gestures should be recognized
simultaneous-handlers Array [] no Declarate gesture nodes that can be triggered at the same time
native-view string nothing no Native node types of the agent

Native-viewSupported enumeration values arescroll-viewandswiper。The 'gesture component proxy internal gestures when the scroll container is scrolled lengthwise, and' when scrolling container is scrolled laterally.

  • Eventhandlertype is event callback, no return value
  • The callbacktype is a callback function registered by the developer to the component, which is executed at the appropriate time to read the callback value
  • All callbacks can pass in only one worklet callback

# Event callback parameters

# worklet:should-response-on-move

The returned parameterspointerEventThe fields are as follows.When each touch move is callbacked, the corresponding gesture component cannot receive thefalseevent when themoveevent is returned.

attribute type Introductions
identifier number Unique identifier for a touch object
type string Type of event
deltaX number The coordinates that moved in the x-axis relative to the last time
deltaY number The coordinates that moved in the Y direction relative to the last time
clientX number The X coordinate of the contact point relative to the left edge of the visible visual area
clientY number The Y coordinate of the contact point relative to the upper edge of the visible area
radiusX number Returns the horizontal axis (X axis) radius of the smallest ellipse capable of enclosing the contact area
radiusY number Returns the vertical axis (Y axis) radius of the smallest ellipse capable of enclosing the contact area
rotationAngle number Returns an angle value representing the angle at which the ellipse described above by radiusX and radiusY needs to be rotated clockwise in order to cover as precisely as possible the area of contact between the user and the plane
force number The user's pressure on the touch plane
timeStamp number The timestamp triggered by the event
Page({
  shouldResponseOnMove(pointerEvent) {
    'worklet'
    return false
  }
})

# worklet:should-accept-gesture

As follows, the framework callbacks when the gesture recognition takes effect, leaving it up to the developer to decide whether the gesture will take effect.Take thePangesture as an example.

State.PossibleState,shouldAcceptGestureReturnfalseEnter afterState.CANCELLEDState,Returningtrueenters theState.Startstate to continue receiving the proceduremoveevent.

Page({
  shouldAcceptGesture() {
    'worklet'
    return false
  }
})

# worklet:ongesture

Different types of gesture components return the following parameters

# tap / double-tap

attribute type Introductions
state number Gesture Status
absoluteX number The X coordinate relative to the global
absoluteY number The Y coordinate relative to the global

# pan / vertical-drag / horizontal-drag

attribute type Introductions
state number Gesture Status
absoluteX number The X coordinate relative to the global
absoluteY number The Y coordinate relative to the global
deltaX number The coordinates that moved in the x-axis relative to the last time
deltaY number The coordinates that moved in the Y direction relative to the last time
velocityX number Lateral speed of the finger away from the screen (pixel per second)
velocityY number Vertical speed of the finger away from the screen (pixel per second)

# scale

attribute type Introductions
state number Gesture Status
focalX number The X coordinate of the center point relative to the global
focalY number Y coordinate of the center point relative to the global
focalDeltaX number The coordinate of the center point moving in the X axis relative to the last time
focalDeltaY number Relative to the last time, the coordinate of the center point moving in the Y axis
scale number Proportions that are enlarged or decreased
horizontalScale number Lateral component of scale
verticalScale number Longitudinal component of scale
rotation number Angle of swing (in radians)
velocityX number Lateral speed of the finger away from the screen (pixel per second)
velocityY number Vertical speed of the finger away from the screen (pixel per second)
pointerCount number Tracking the Hand Index
  • When multi-finger swiping,focalXandfocalYare coordinates of the center focus of multiple touch points
  • When a single finger slides,pointerCount = 1, the effect is the same aspan-gesture-handlerscaleGesture is a superset ofpan.

# long-press

attribute type Introductions
state number Gesture Status
absoluteX number The X coordinate relative to the global
absoluteY number The Y coordinate relative to the global
translationX number X-axis offset relative to the initial touch point
translationY number Y-axis offset relative to the initial touch point
velocityX number Lateral speed of the finger away from the screen (pixel per second)
velocityY number Vertical speed of the finger away from the screen (pixel per second)

# force-press

attribute type Introductions
state number Gesture Status
absoluteX number The X coordinate relative to the global
absoluteY number The Y coordinate relative to the global
pressure number The pressure size

# Gesture Status

All gestureworklet: ongesturecallbacks return astatestatus field.

enum State {
  // 手势未识别
  POSSIBLE = 0,
  // 手势已识别
  BEGIN = 1,
  // 连续手势活跃状态
  ACTIVE = 2,
  // 手势终止
  END = 3,
  // 手势取消
  CANCELLED = 4,
}

We divide hand gestures into the following two types:

  1. Discrete gestures:tapanddouble-tap, triggered only once
  2. Continuous gestures: Other types of gesture components that are triggered multiple times with dragging a finger

The tap-gesture-handlerThe gesture component returns thestateis always 1.

Pan-gesture-handlergesture component During a complete drag, thestatechanges as follows

  1. When the finger touches the screen, thestate = 0
  2. Move a short distance,panWhen the gesture decision takes effect,state = 1
  3. Continue moving,state = 2
  4. Finger off screenstate = 3

Since nested gestures can create conflicts (only one final judgment of recognition takes effect),Thus, changes in continuous gesturesstatemay occur in the following scenarios, and developers need to handle some exceptions based on thestate]]value.

  1. POSSIBLE - > BEGIN - > ACTIVE - > ENDNormal process
  2. POSSIBLE - > BEGIN - > ACTIVE - > CANCELLEDEARLY INTERRUPTION
  3. POSSIBLE - > CANCELLEDGesture not recognized

Not all continuous gestures have aPOSSIBLEstate, such asscale-gesture-handlerThe gesture component, when two fingers touch and release, thestatechanges as follows:

  1. Two-fingered touch screen,state = 1, pointerCount = 2
  2. Double finger magnification operation,state = 2, pointerCount = 2
  3. Two fingers leave the screen,state = 3, pointerCount = 1, and then callback a.state = 1, pointerCount = 1 b.state = 2, pointerCount = 1 c.state = 3, pointerCount = 0

# Note

  • Gesture components can only be used inSkylinerendering mode
  • The gesture component is a virtual component and will not be layouted. Set on the gesture componentstyle,classis invalid
  • The gesture component can contain only one direct sub-node, otherwise it does not take effect
  • The parent component style of a gesture component directly affects its child nodes
  • All callbacks of gesture components must be declared as worklet
  • The gesture is different from the normaltouchevent and does not bubble
  • eventhandler / callbackIs declared as aworkletfunction, and the callback is triggered in theUIthread

# How to use it

# sample code

Preview the effect in the developer tool

# ChainingAPI init function sample code

Preview the effect in the developer tool

# Example 1: Listen to drag and drop gestures

<pan-gesture-handler on-gesture-event="handlePan">
  <view></view>
</pan-gesture-handler>
Page({
  handlePan(evt) {
    "worklet";
    console.log(evt.translateX);
  },
});

# Example 2: Listen for nested gestures

<horizontal-drag-gesture-handler on-gesture-event="handleHorizontalDrag">
  <vertical-drag-gesture-handler on-gesture-event="handleVerticalDrag">
    <view class="circle">one-way drag</view>
  </vertical-drag-gesture-handler>
</horizontal-drag-gesture-handler>

# Example 3: Internal gestures in a proxy native component

For rolling containers like 'and``, the inside is also gesture-based to handle rolling operations.Compared toweb,skyline'A lower-level access mechanism is provided, allowing for more granular and phased control when doing some complex interactions.

<vertical-drag-gesture-handler
  native-view="scroll-view"
  should-response-on-move="shouldScrollViewResponse"
  should-accept-gesture="shouldScrollViewAccept"
  on-gesture-event="handleGesture"
>
  <scroll-view
    scroll-y
    type="list"
    adjust-deceleration-velocity="adjustDecelerationVelocity"
    bindscroll="handleScroll"
  >
    <view class="item" wx:for="{{list}}">
      <view class="avatar" />
      <view class="comment" />
    </view>
  </scroll-view>
</vertical-drag-gesture-handler>

In the case of a vertically scrolled'', you can use the``gesture component and declarenative-view = "scroll-view" 'to proxy its internal gestures.

# Scrolling events

When scrolling the list, both the gesture component event callback and the'scroll' event callback are triggered, the difference being:

  1. The scrollevent is triggered only when scrolling, and when the top / bottom is reached, it is not called back
  2. The on-gesture-eventThe gesture callback is triggered when the finger slides on the screen until you let go

# Gesture Control

In the previous introduction of continuous gesture states, we knew that gestures have their own process of identification.For examplevertical-dragA gesture, which is inPOSSIBLEwhen touched by a finger, is recognized after moving a short distance asBEGIN, at which point the gesture is recognizable (]]ACCEPT)。

# 1. Gesture recognition

The should-accept-gestureattribute allows developers to register acallback,And returns a Boolean value that participates in the process of gesture recognition .Whenfalseis returned, the touch gesture is no longer valid and the associated component cannot be rolled.

# 2. Event distribution

The should-response-on-moveattribute allows developers to register acallback,And returns a Boolean value that participates in the event dispatch process.Whenfalseis returned, the eventmoveis no longer sent, and the associated `` does not continue to scroll.This callback is continuously triggered as the finger moves and can be changed at any time to control the scrolling container to continue / pause the scrolling.

Page({
  // 这里返回 false,则 scroll-view 无法滚动
  // should-accept-gesture 会在手势识别的一开始触发一次
  // should-response-on-move 是在 move 过程中不断触发
  shouldScrollViewAccept() {
    'worklet'
    return true
  },

  // 这里返回 false,则 scroll-view 无法滚动
  shouldScrollViewResponse(pointerEvent) {
    'worklet';
    return true;
  },

  // 手指滑动离开滚动组件时,指定衰减速度
  adjustDecelerationVelocity(velocity) {
    'worklet';
   return velocity;
  },

  // scroll-view 滚动到边界后,手指滑动,scroll 事件不再触发
  handleScroll(evt) {
    'worklet';
  },

  // scroll-view 滚动到边界后,手指滑动,手势回调仍然会触发
  handleGesture(evt) {
    'worklet'
  },
});

# Example 4: Gesture consultation

In some scenarios, we will encounter gesture conflict .As shown in the following code, there are nested 'components, and we want theouterThegesture component handles longitudinal drag, and theinnergestures component handles list scrolling, but only theinner' gesture callback is actually triggered.

Embedding the same type of gesture component, when the gesture in the inner layer is recognized, the gesture component in the outer layer will not be recognized.

<vertical-drag-gesture-handler tag="outer">
  <vertical-drag-gesture-handler tag="inner" native-view="scroll-view">
    <scroll-view scroll-y></scroll-view>
  </vertical-drag-gesture-handler>
</vertical-drag-gesture-handler>

However, such scenarios are quite common, as seen in the comment lists of WeChat Channels, where the scrolling of the list and the dragging of the entire comment section seamlessly integrate.Gesture negotiation mechanism is used to solve this kind of problem, and it is also very simple to use.simultaneous-handlersattribute states that multiple gestures can be triggered simultaneously.

<vertical-drag-gesture-handler tag="outer" simultaneous-handlers="{{["inner"]}}">
  <vertical-drag-gesture-handler tag="inner" simultaneous-handlers="{{["outer"]}}" native-view="scroll-view">
    <scroll-view scroll-y></scroll-view>
  </vertical-drag-gesture-handler>
</vertical-drag-gesture-handler>

At this point,outerandinnerTheon-gesture-eventcallback of the gesture component is triggered in succession, combined with the gesture control principle mentioned above, to achieve the desired effect.Full code reference sample demo .