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:
- 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);
- 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;
- 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:
- 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));
- 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
pointerTypeproperty 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) orevt.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
isPencilActivestate and re-enable scrolling immediately.
内容的提问来源于stack exchange,提问作者Nadine




