You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React Native中如何区分Apple Pencil触控与手指触摸事件?

Great question! This is a common scenario when building drawing-focused React Native apps on iOS, and the key here is leveraging the platform's ability to distinguish between touch sources via the pointerType property. Let's break down a solid solution using both PanResponder (the classic approach) and React Native Gesture Handler (the more modern, performant option):

Solution Overview

The core idea is straightforward:

  • Detect if the incoming touch event comes from a finger (pointerType: 'touch') or Apple Pencil (pointerType: 'pen')
  • For finger touches: Let the ScrollView handle scrolling, and block any drawing logic
  • For Apple Pencil touches: Take over the gesture to handle drawing, and disable ScrollView scrolling temporarily

Using PanResponder (Classic Approach)

PanResponder is React Native's built-in gesture system, which works reliably for this use case. Here's a step-by-step implementation:

  1. First, set up state to track if Apple Pencil is active:
import { useState, useRef } from 'react';
import { View, ScrollView, PanResponder } from 'react-native';

const DrawableList = () => {
  const [isPencilActive, setIsPencilActive] = useState(false);
  // Replace with your actual list of drawable areas
  const drawableAreas = Array(5).fill(null);
  1. Create the PanResponder to handle gesture detection and drawing logic:
const panResponder = useRef(
    PanResponder.create({
      // Only take control of the gesture if the input is Apple Pencil
      onStartShouldSetPanResponder: (evt) => {
        const isPencil = evt.nativeEvent.pointerType === 'pen';
        setIsPencilActive(isPencil);
        return isPencil;
      },

      // Handle drawing updates when the Pencil moves
      onPanResponderMove: (evt, gestureState) => {
        if (isPencilActive) {
          // Update your drawing path here using gestureState or event coordinates
          console.log(`Drawing at X: ${evt.nativeEvent.locationX}, Y: ${evt.nativeEvent.locationY}`);
        }
      },

      // Reset state when the Pencil leaves the screen
      onPanResponderRelease: () => {
        setIsPencilActive(false);
      },
    })
  ).current;
  1. Render the ScrollView with dynamic scrolling enabled, and attach the PanResponder to each drawable area:
return (
    <ScrollView 
      style={{ flex: 1 }}
      scrollEnabled={!isPencilActive} // Disable scrolling when Pencil is in use
    >
      {drawableAreas.map((_, index) => (
        <View
          key={index}
          {...panResponder.panHandlers}
          style={{
            height: 200,
            margin: 16,
            backgroundColor: '#f0f0f0',
            borderWidth: 1,
            borderColor: '#ccc',
          }}
        >
          {/* Your drawing canvas/element goes here (e.g., a custom View or react-native-skia component) */}
        </View>
      ))}
    </ScrollView>
  );
};

export default DrawableList;

Using React Native Gesture Handler (Modern, Performant)

If you're using React Native Gesture Handler (recommended for smoother, more responsive gestures), the implementation is similar but more streamlined:

  1. Install the library first (if you haven't already), then set up the gesture:
import { useState } from 'react';
import { View, ScrollView } from 'react-native';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

const DrawableList = () => {
  const [isPencilActive, setIsPencilActive] = useState(false);
  const drawableAreas = Array(5).fill(null);

  const panGesture = Gesture.Pan()
    .onBegin((evt) => {
      const isPencil = evt.pointerType === 'pen';
      setIsPencilActive(isPencil);
      // Only proceed with the gesture if it's a Pencil input
      return isPencil;
    })
    .onUpdate((evt) => {
      if (isPencilActive) {
        // Handle drawing logic here
        console.log(`Drawing at X: ${evt.x}, Y: ${evt.y}`);
      }
    })
    .onEnd(() => setIsPencilActive(false));
  1. Render the ScrollView with GestureDetector wrapping each drawable area:
return (
    <ScrollView 
      style={{ flex: 1 }}
      scrollEnabled={!isPencilActive}
    >
      {drawableAreas.map((_, index) => (
        <GestureDetector key={index} gesture={panGesture}>
          <View
            style={{
              height: 200,
              margin: 16,
              backgroundColor: '#f0f0f0',
              borderWidth: 1,
              borderColor: '#ccc',
            }}
          >
            {/* Your drawing canvas */}
          </View>
        </GestureDetector>
      ))}
    </ScrollView>
  );
};

export default DrawableList;

Key Notes

  • iOS Version Support: The pointerType property is available in React Native 0.60+ and works on iOS 13+.
  • Additional Pencil Data: If you need pressure or tilt information, you can access evt.nativeEvent.force (pressure) or evt.nativeEvent.altitudeAngle (tilt) in PanResponder, or directly from the gesture event in React Native Gesture Handler.
  • Edge Cases: Make sure to handle abrupt Pencil disconnections (like lifting it quickly) to reset the isPencilActive state and re-enable scrolling immediately.

内容的提问来源于stack exchange,提问作者Nadine

火山引擎 最新活动