API

nsh/gesture

Pan responder 是构建在手势响应者系统之上的拖动手势:元素认领一次手势的触摸,然后独自接收这些触摸和手势状态,手势状态会帮你跟踪各触点的中心、移动距离和速度。Rust 代码用 .claim_touches(TouchClaim::…) 认领触摸,用 .on_responder_* 监听,其 ResponderEvent 带有 PanGesture,都在 nsh_core::ui::prelude 中;TSX 从 nsh/gesture 导入 PanResponder,它的 onPanResponder… 回调接收触摸事件和手势状态。

use nsh_core::ui::prelude::*; // TouchClaim、ClaimRequest、ResponderEvent、PanGesture

#PanResponder.create

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

interface PanResponderInstance {
  panHandlers: TouchProps;
}

创建一个 pan responder。把它的 panHandlers 展开到需要接收手势的视图上。在 Rust 中,由视图自己认领触摸并监听:

use nsh_core::ui::prelude::*;

#[component]
pub fn App() -> impl IntoElement {
    let pos = signal((0.0_f32, 0.0_f32));
    let start = stored((0.0_f32, 0.0_f32));
    view().flex_1().child(
        view()
            .size(px(64.0))
            .rounded(px(32.0))
            .bg(rgb(0x6366f1))
            .style_with(move |s| {
                let (x, y) = pos.get();
                s.translate_x(px(x)).translate_y(px(y))
            })
            .claim_touches(TouchClaim::on_start())
            .on_responder_grant(move |_: &ResponderEvent| {
                start.replace(pos.get_untracked());
            })
            .on_responder_move(move |e: &ResponderEvent| {
                let (x, y) = start.get();
                pos.set((x + e.gesture.delta.x.0, y + e.gesture.delta.y.0));
            })
            .on_responder_release(move |e: &ResponderEvent| {
                log::info!("released at {:.2} pt/ms", e.gesture.vx);
            }),
    )
}

#回调

每个回调都会收到触摸事件(GestureResponderEvent,来自 nsh)和手势状态。在 Rust 中,认领用 .ask(|request: &ClaimRequest| …) 决定,调用 request.claim() 接管触摸(没有 ask 时总是接管),每个监听器收到一个 ResponderEvent,其 gesture 就是手势状态。

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

#PanResponderGestureState

interface PanResponderGestureState {
  stateID: number;
  moveX: number;               // 触点中心的最新位置(界面坐标,点)
  moveY: number;
  x0: number;                  // 成为响应者时触点中心的位置
  y0: number;
  dx: number;                  // 成为响应者以来移动的距离
  dy: number;
  vx: number;                  // 速度,点/毫秒
  vy: number;
  numberActiveTouches: number;
}

Rust:PanGesture,位置为 Point<Points>。

字段Rust说明
stateID标识这次手势。
moveX、moveYposition触点中心的最新位置,界面坐标(点)。
x0、y0origin成为响应者时触点中心的位置。
dx、dydelta成为响应者以来移动的距离。
vx、vyvx、vy速度,单位为点/毫秒。
numberActiveTouchesactive_touches按下的手指数。

#在滚动视图中

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

view()
    .claim_touches(TouchClaim::on_move().ask(|request: &ClaimRequest| {
        let d = request.event.gesture.delta;
        if d.x.0.abs() > 10.0 && d.x.0.abs() > d.y.0.abs() {
            request.claim();
        }
    }))
    .on_responder_move(move |e: &ResponderEvent| offset.set(e.gesture.delta.x.0))
    .on_responder_release(move |e: &ResponderEvent| {
        if e.gesture.delta.x.0.abs() > 120.0 { remove() } else { offset.set(0.0) }
    })
    .on_responder_terminate(move |_: &ResponderEvent| offset.set(0.0))

对于由平台识别的拖动,TouchClaim::on_pan(PanActivation::horizontal(10.0, 10.0)) 在手指移动时于主线程上决定。底层的响应者属性(onStartShouldSetResponder、onResponderGrant、onTouchStart 等)也可以直接用在任何元素上;见元素。