API

nsh/gesture

nsh/gesture 导出 PanResponder:构建在手势响应者系统之上的拖动手势,通过 onPanResponder… 回调接收触摸事件和手势状态。它会帮你跟踪各触点的中心、移动距离和速度。

import { PanResponder, type PanResponderGestureState } from "nsh/gesture";

#PanResponder.create

const PanResponder: { create(config: PanResponderCallbacks): PanResponderInstance };

interface PanResponderInstance {
  panHandlers: TouchProps;
}

创建一个 pan responder。把它的 panHandlers 展开到需要接收手势的视图上:

import { mount, signal, View } from "nsh";
import { PanResponder } from "nsh/gesture";

function App() {
  const pos = signal({ x: 0, y: 0 });
  let start = { x: 0, y: 0 };
  const pan = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderGrant: () => (start = pos()),
    onPanResponderMove: (_, g) => pos.set({ x: start.x + g.dx, y: start.y + g.dy }),
    onPanResponderRelease: (_, g) => console.log(`released at ${g.vx.toFixed(2)} pt/ms`),
  });
  return (
    <View style={{ flex: 1 }}>
      <View
        {...pan.panHandlers}
        style={{
          width: 64,
          height: 64,
          borderRadius: 32,
          backgroundColor: "#6366f1",
          transform: [{ translateX: pos().x }, { translateY: pos().y }],
        }}
      />
    </View>
  );
}

mount(App);

#回调

每个回调都会收到触摸事件(GestureResponderEvent,来自 nsh)和手势状态。

type PanHandler<R = void> = (e: GestureResponderEvent, gestureState: PanResponderGestureState) => R;
回调返回值调用时机
onStartShouldSetPanResponderboolean手指按下:返回 true 以成为响应者。从被触摸的视图向外依次询问。
onStartShouldSetPanResponderCaptureboolean同上,但在冒泡询问之前从外向内询问(父视图从子视图手中接管手势)。
onMoveShouldSetPanResponderboolean手指移动:返回 true 以成为响应者(例如仅当 Math.abs(g.dx) > 10 时)。
onMoveShouldSetPanResponderCaptureboolean同上,捕获阶段。
onPanResponderGrant该视图成为响应者:手势开始。
onPanResponderReject另一个视图是响应者且不肯放手。
onPanResponderStart响应期间有手指按下。
onPanResponderMove手指移动。
onPanResponderEnd响应期间有手指抬起。
onPanResponderRelease最后一根手指抬起:手势结束。
onPanResponderTerminate另一个视图(或原生手势,比如滚动视图)接管了触摸。
onPanResponderTerminationRequestboolean另一个视图想要接管触摸:返回 false 以保留(默认 true)。
onShouldBlockNativeResponderboolean响应期间是否阻止原生滚动视图接管触摸(默认 true)。

#PanResponderGestureState

interface PanResponderGestureState {
  stateID: number;
  moveX: number;               // 触点中心的最新位置(界面坐标,点)
  moveY: number;
  x0: number;                  // 成为响应者时触点中心的位置
  y0: number;
  dx: number;                  // 成为响应者以来移动的距离
  dy: number;
  vx: number;                  // 速度,点/毫秒
  vy: number;
  numberActiveTouches: number;
}
字段说明
stateID标识这次手势。
moveX、moveY触点中心的最新位置,界面坐标(点)。
x0、y0成为响应者时触点中心的位置。
dx、dy成为响应者以来移动的距离。
vx、vy速度,单位为点/毫秒。
numberActiveTouches按下的手指数。

#在滚动视图中

当 pan responder 作为响应者时,外层的原生滚动视图不会滚动(onShouldBlockNativeResponder 默认为 true)。如果要让横向滑动与纵向滚动共存,只在横向移动时成为响应者:

const swipe = PanResponder.create({
  onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 10 && Math.abs(g.dx) > Math.abs(g.dy),
  onPanResponderMove: (_, g) => offset.set(g.dx),
  onPanResponderRelease: (_, g) => (Math.abs(g.dx) > 120 ? remove() : offset.set(0)),
  onPanResponderTerminate: () => offset.set(0),
});

底层的响应者属性(onStartShouldSetResponder、onResponderGrant、onTouchStart 等)也可以直接用在任何元素上;见元素。