React Native事件处理 #

概述 #

React Native 提供了多种处理触摸事件和手势的方式。本章节介绍如何处理用户交互,包括基本触摸事件、手势识别和交互反馈。

基本触摸组件 #

TouchableOpacity #

最常用的可触摸组件,点击时透明度降低:

tsx
import React from 'react';
import {TouchableOpacity, Text, StyleSheet, Alert} from 'react-native';

const TouchableOpacityExample = () => {
  const handlePress = () => {
    Alert.alert('提示', '按钮被点击了');
  };

  const handleLongPress = () => {
    Alert.alert('提示', '长按触发');
  };

  return (
    <TouchableOpacity
      style={styles.button}
      onPress={handlePress}
      onLongPress={handleLongPress}
      activeOpacity={0.7}>
      <Text style={styles.text}>Click Me</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  text: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

export default TouchableOpacityExample;

TouchableHighlight #

点击时背景变暗:

tsx
import React from 'react';
import {TouchableHighlight, Text, StyleSheet, View} from 'react-native';

const TouchableHighlightExample = () => {
  return (
    <TouchableHighlight
      style={styles.button}
      underlayColor="#0056b3"
      onPress={() => console.log('Pressed')}>
      <Text style={styles.text}>TouchableHighlight</Text>
    </TouchableHighlight>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
  },
  text: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
    textAlign: 'center',
  },
});

TouchableNativeFeedback(Android) #

Android 原生水波纹效果:

tsx
import React from 'react';
import {
  TouchableNativeFeedback,
  Text,
  StyleSheet,
  View,
  Platform,
} from 'react-native';

const NativeButton = () => {
  if (Platform.OS === 'android') {
    return (
      <TouchableNativeFeedback
        onPress={() => console.log('Pressed')}
        background={TouchableNativeFeedback.Ripple('#007AFF', false)}
        useForeground>
        <View style={styles.button}>
          <Text style={styles.text}>Native Button</Text>
        </View>
      </TouchableNativeFeedback>
    );
  }

  return (
    <TouchableOpacity style={styles.button} onPress={() => console.log('Pressed')}>
      <Text style={styles.text}>iOS Button</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
  },
  text: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

TouchableWithoutFeedback #

无视觉反馈,用于隐藏键盘等场景:

tsx
import React from 'react';
import {
  TouchableWithoutFeedback,
  Keyboard,
  View,
  Text,
  TextInput,
  StyleSheet,
} from 'react-native';

const HideKeyboardExample = () => {
  return (
    <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
      <View style={styles.container}>
        <TextInput style={styles.input} placeholder="Tap outside to dismiss" />
      </View>
    </TouchableWithoutFeedback>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 20,
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    paddingHorizontal: 16,
  },
});

Pressable 组件 #

Pressable 是 React Native 0.63 引入的新组件,提供更强大的交互能力:

tsx
import React from 'react';
import {Pressable, Text, StyleSheet} from 'react-native';

const PressableExample = () => {
  return (
    <Pressable
      onPress={() => console.log('Pressed')}
      onLongPress={() => console.log('Long pressed')}
      onPressIn={() => console.log('Press in')}
      onPressOut={() => console.log('Press out')}
      delayLongPress={500}
      hitSlop={10}
      style={({pressed}) => [
        styles.button,
        pressed && styles.pressed,
      ]}>
      {({pressed}) => (
        <Text style={styles.text}>
          {pressed ? 'Pressed!' : 'Press Me'}
        </Text>
      )}
    </Pressable>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
  },
  pressed: {
    backgroundColor: '#0056b3',
  },
  text: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

Pressable 属性 #

属性 类型 说明
onPress function 点击回调
onLongPress function 长按回调
onPressIn function 按下回调
onPressOut function 抬起回调
disabled boolean 是否禁用
delayLongPress number 长按延迟(毫秒)
hitSlop number/object 扩大触摸区域
pressRetentionOffset number/object 触摸保留区域

手势响应系统 #

基本手势事件 #

tsx
import React, {useState} from 'react';
import {View, StyleSheet, GestureResponderEvent} from 'react-native';

const GestureExample = () => {
  const [position, setPosition] = useState({x: 0, y: 0});

  const handleTouchStart = (e: GestureResponderEvent) => {
    const {locationX, locationY} = e.nativeEvent;
    setPosition({x: locationX, y: locationY});
  };

  const handleTouchMove = (e: GestureResponderEvent) => {
    const {locationX, locationY} = e.nativeEvent;
    setPosition({x: locationX, y: locationY});
  };

  return (
    <View
      style={styles.container}
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={() => console.log('Touch ended')}>
      <View style={[styles.indicator, {left: position.x - 25, top: position.y - 25}]} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  indicator: {
    position: 'absolute',
    width: 50,
    height: 50,
    borderRadius: 25,
    backgroundColor: '#007AFF',
  },
});

PanResponder #

PanResponder 提供了更高级的手势处理能力:

tsx
import React, {useRef, useState} from 'react';
import {View, PanResponder, StyleSheet, Animated} from 'react-native';

const DraggableBox = () => {
  const pan = useRef(new Animated.ValueXY()).current;

  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onPanResponderGrant: () => {
        pan.setOffset({
          x: (pan.x as any)._value,
          y: (pan.y as any)._value,
        });
        pan.setValue({x: 0, y: 0});
      },
      onPanResponderMove: Animated.event([null, {dx: pan.x, dy: pan.y}], {
        useNativeDriver: false,
      }),
      onPanResponderRelease: () => {
        pan.flattenOffset();
      },
    }),
  ).current;

  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.box,
          {transform: [{translateX: pan.x}, {translateY: pan.y}]},
        ]}
        {...panResponder.panHandlers}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    borderRadius: 8,
  },
});

export default DraggableBox;

滚动事件 #

ScrollView 滚动事件 #

tsx
import React, {useState, useRef} from 'react';
import {ScrollView, Text, View, StyleSheet} from 'react-native';

const ScrollEventExample = () => {
  const [scrollY, setScrollY] = useState(0);
  const scrollViewRef = useRef<ScrollView>(null);

  const handleScroll = (event: any) => {
    const {y} = event.nativeEvent.contentOffset;
    setScrollY(y);
  };

  const handleMomentumScrollEnd = (event: any) => {
    console.log('Scroll ended at:', event.nativeEvent.contentOffset.y);
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text>Scroll Position: {Math.round(scrollY)}</Text>
      </View>
      <ScrollView
        ref={scrollViewRef}
        style={styles.scrollView}
        onScroll={handleScroll}
        onMomentumScrollEnd={handleMomentumScrollEnd}
        scrollEventThrottle={16}>
        {Array.from({length: 50}).map((_, index) => (
          <View key={index} style={styles.item}>
            <Text>Item {index + 1}</Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  header: {
    padding: 16,
    backgroundColor: '#007AFF',
  },
  scrollView: {
    flex: 1,
  },
  item: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
});

键盘事件 #

键盘显示/隐藏监听 #

tsx
import React, {useState, useEffect} from 'react';
import {Keyboard, View, Text, TextInput, StyleSheet} from 'react-native';

const KeyboardExample = () => {
  const [keyboardHeight, setKeyboardHeight] = useState(0);

  useEffect(() => {
    const keyboardWillShow = Keyboard.addListener('keyboardWillShow', e => {
      setKeyboardHeight(e.endCoordinates.height);
    });

    const keyboardWillHide = Keyboard.addListener('keyboardWillHide', () => {
      setKeyboardHeight(0);
    });

    return () => {
      keyboardWillShow.remove();
      keyboardWillHide.remove();
    };
  }, []);

  return (
    <View style={[styles.container, {paddingBottom: keyboardHeight}]}>
      <Text>Keyboard Height: {keyboardHeight}</Text>
      <TextInput style={styles.input} placeholder="Type something" />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    paddingHorizontal: 16,
    marginTop: 16,
  },
});

KeyboardAvoidingView #

自动避开键盘:

tsx
import React from 'react';
import {
  KeyboardAvoidingView,
  TextInput,
  View,
  Text,
  StyleSheet,
  Platform,
} from 'react-native';

const KeyboardAvoidingExample = () => {
  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 0}>
      <View style={styles.content}>
        <Text>Form Content</Text>
        <TextInput style={styles.input} placeholder="Input 1" />
        <TextInput style={styles.input} placeholder="Input 2" />
        <TextInput style={styles.input} placeholder="Input 3" />
      </View>
    </KeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  content: {
    flex: 1,
    padding: 16,
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    paddingHorizontal: 16,
    marginBottom: 16,
  },
});

扩大触摸区域 #

hitSlop #

tsx
import React from 'react';
import {TouchableOpacity, View, StyleSheet} from 'react-native';

const HitSlopExample = () => {
  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.smallButton}
        hitSlop={{top: 20, bottom: 20, left: 20, right: 20}}
        onPress={() => console.log('Pressed')}>
        <View style={styles.innerButton} />
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  smallButton: {
    width: 44,
    height: 44,
    justifyContent: 'center',
    alignItems: 'center',
  },
  innerButton: {
    width: 20,
    height: 20,
    backgroundColor: '#007AFF',
    borderRadius: 10,
  },
});

禁用交互 #

tsx
import React, {useState} from 'react';
import {TouchableOpacity, Text, StyleSheet} from 'react-native';

const DisabledButton = () => {
  const [isLoading, setIsLoading] = useState(false);

  const handlePress = async () => {
    setIsLoading(true);
    await someAsyncOperation();
    setIsLoading(false);
  };

  return (
    <TouchableOpacity
      style={[styles.button, isLoading && styles.disabled]}
      onPress={handlePress}
      disabled={isLoading}>
      <Text style={styles.text}>
        {isLoading ? 'Loading...' : 'Submit'}
      </Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
  },
  disabled: {
    backgroundColor: '#ccc',
  },
  text: {
    color: '#fff',
    fontSize: 16,
  },
});

实用示例 #

可滑动删除 #

tsx
import React, {useRef} from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  Animated,
  PanResponder,
  StyleSheet,
} from 'react-native';

interface SwipeToDeleteProps {
  text: string;
  onDelete: () => void;
}

const SwipeToDelete: React.FC<SwipeToDeleteProps> = ({text, onDelete}) => {
  const translateX = useRef(new Animated.Value(0)).current;

  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: (_, gestureState) => {
        return Math.abs(gestureState.dx) > Math.abs(gestureState.dy);
      },
      onPanResponderMove: (_, gestureState) => {
        if (gestureState.dx < 0) {
          translateX.setValue(gestureState.dx);
        }
      },
      onPanResponderRelease: (_, gestureState) => {
        if (gestureState.dx < -100) {
          Animated.timing(translateX, {
            toValue: -200,
            duration: 200,
            useNativeDriver: true,
          }).start(() => {
            onDelete();
          });
        } else {
          Animated.spring(translateX, {
            toValue: 0,
            useNativeDriver: true,
          }).start();
        }
      },
    }),
  ).current;

  return (
    <View style={styles.container}>
      <View style={styles.deleteButton}>
        <Text style={styles.deleteText}>Delete</Text>
      </View>
      <Animated.View
        style={[styles.content, {transform: [{translateX}]}]}
        {...panResponder.panHandlers}>
        <Text>{text}</Text>
      </Animated.View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#FF3B30',
    marginVertical: 4,
  },
  deleteButton: {
    position: 'absolute',
    right: 0,
    top: 0,
    bottom: 0,
    width: 100,
    justifyContent: 'center',
    alignItems: 'center',
  },
  deleteText: {
    color: '#fff',
    fontWeight: 'bold',
  },
  content: {
    backgroundColor: '#fff',
    padding: 16,
  },
});

export default SwipeToDelete;

总结 #

React Native 提供了丰富的交互处理能力:

  • TouchableOpacity:透明度反馈
  • TouchableHighlight:背景高亮
  • TouchableNativeFeedback:Android 水波纹
  • Pressable:新式可按压组件
  • PanResponder:高级手势处理
  • Keyboard:键盘事件管理

掌握这些交互处理方式,可以创建流畅的用户体验。

继续学习 React Navigation基础,了解导航系统的使用。

最后更新:2026-03-28