React Native Academy · 课时

使用 TouchableOpacity 与 Pressable 处理点击

使用 TouchableOpacity 和较新的 Pressable API 为界面元素添加点击反馈,包括按下和松开状态的视觉反馈。

第 2 / 4 课13 个步骤

使用 TouchableOpacity 与 Pressable 处理点击 是 CoddyKit 上的免费 React Native Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Native Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Native Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Making Elements Tappable

In React Native, most components like View and Text are not tappable by default — they ignore touch events. To make something respond to taps, wrap it in a touchable component. React Native provides several options: TouchableOpacity (fades the element on press), TouchableHighlight (darkens the background on press), TouchableWithoutFeedback (no visual feedback), and the newer Pressable API (most flexible). TouchableOpacity and Pressable are the two you'll use for the vast majority of UI elements.

import { TouchableOpacity, Text, View } from 'react-native';

export default function TapExample() {
  return (
    <View style={{ padding: 24 }}>
      <TouchableOpacity
        onPress={() => console.log('Tapped!')}
        style={{ backgroundColor: '#4f86f7', padding: 16, borderRadius: 10 }}
      >
        <Text style={{ color: '#fff', textAlign: 'center', fontWeight: 'bold' }}>
          Tap Me
        </Text>
      </TouchableOpacity>
    </View>
  );
}

TouchableOpacity and activeOpacity

TouchableOpacity is the most commonly used touchable component. When pressed, it reduces the opacity of its children to give visual feedback that the touch was registered. The default activeOpacity is 0.2 (80% fade). Customize it with the activeOpacity prop: 0.7 for a subtle feedback, 0.9 for almost no change, or 0.1 for a strong fade. The fade animation runs on the native thread, making it smooth even when the JS thread is busy processing other events — this is what keeps it feeling responsive.

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

export default function Buttons() {
  return (
    <View style={styles.col}>
      <TouchableOpacity
        activeOpacity={0.8}  // subtle fade
        onPress={() => {}}
        style={styles.btn}
      >
        <Text style={styles.label}>Subtle (0.8)</Text>
      </TouchableOpacity>

      <TouchableOpacity
        activeOpacity={0.2}  // strong fade (default)
        onPress={() => {}}
        style={styles.btn}
      >
        <Text style={styles.label}>Strong (0.2)</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  col: { padding: 16, gap: 12 },
  btn: { backgroundColor: '#4f86f7', padding: 16, borderRadius: 10, alignItems: 'center' },
  label: { color: '#fff', fontWeight: '600', fontSize: 16 },
});

onPress, onLongPress, and onPressIn/Out

TouchableOpacity fires several touch events. onPress fires after a complete tap (touch down + touch up). onLongPress fires after the user holds down for ~500ms — useful for context menus or delete confirmations. onPressIn fires immediately when the finger touches the screen (before lift), and onPressOut fires when the finger lifts. onPressIn is used to trigger immediate visual feedback or start animations before the tap completes. Use delayLongPress to customize how long the user must hold before onLongPress fires.

import { TouchableOpacity, Text } from 'react-native';

export default function TouchEvents() {
  return (
    <TouchableOpacity
      onPressIn={() => console.log('Touch started')}
      onPressOut={() => console.log('Touch ended')}
      onPress={() => console.log('Tap completed!')}
      onLongPress={() => console.log('Long pressed!')}
      delayLongPress={700} // ms before onLongPress fires
      style={{ backgroundColor: '#4f86f7', padding: 20, borderRadius: 10, margin: 20 }}
    >
      <Text style={{ color: '#fff', textAlign: 'center' }}>
        Tap or hold me
      </Text>
    </TouchableOpacity>
  );
}

Pressable: The Modern API

Pressable is the newer, more flexible touchable API introduced in React Native 0.63. Its key advantage is that the style and children props can accept functions that receive the current press state ({ pressed: boolean }), making dynamic styling on press trivial without managing separate state variables. Pressable also supports more fine-grained event handling including hitSlop, pressRetentionOffset, and unstable_pressDelay. For new code, prefer Pressable over TouchableOpacity.

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

export default function PressableButton({ label, onPress }) {
  return (
    <Pressable
      onPress={onPress}
      style={({ pressed }) => [
        styles.btn,
        pressed && styles.pressed,  // apply when finger is down
      ]}
    >
      {({ pressed }) => (
        <Text style={[styles.label, pressed && styles.pressedLabel]}>
          {pressed ? 'Holding...' : label}
        </Text>
      )}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  btn: { backgroundColor: '#4f86f7', padding: 16, borderRadius: 12, alignItems: 'center' },
  pressed: { backgroundColor: '#2563d4', transform: [{ scale: 0.97 }] },
  label: { color: '#fff', fontWeight: '700', fontSize: 16 },
  pressedLabel: { color: '#c3d8ff' },
});

hitSlop: Expanding the Tap Area

Mobile usability guidelines recommend a minimum tap target size of 44×44 points (iOS) or 48×48 dp (Android). Small icons and text links often don't meet this threshold visually, but you can expand the tappable area with hitSlop. It adds invisible space around the component that still responds to touches. Pass an object with top, bottom, left, right values (in points). This lets you keep a small visual icon while ensuring it's easy to tap — critical for navigation arrows, close buttons, and action icons in list rows.

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

export default function SmallButton() {
  return (
    <View style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}>
      <Text style={{ flex: 1, fontSize: 16 }}>Notification item</Text>

      {/* Small X button — hitSlop expands the tap area */}
      <TouchableOpacity
        onPress={() => console.log('Dismissed')}
        hitSlop={{ top: 16, bottom: 16, left: 16, right: 16 }}
      >
        <Text style={styles.closeIcon}>✕</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  closeIcon: { fontSize: 16, color: '#999', padding: 4 },
});

Disabled State for Buttons

Use the disabled prop on TouchableOpacity or Pressable to prevent interaction. A disabled touchable ignores all touch events. Visually indicate the disabled state by reducing opacity or changing the color — React Native doesn't do this automatically. Use the opacity style or apply a conditional style based on the disabled prop. Disabled buttons are essential for preventing double-submission of forms, indicating that required fields are incomplete, or showing that a feature requires a premium subscription.

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

function ActionButton({ label, onPress, disabled }) {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled}
      style={[styles.btn, disabled && styles.btnDisabled]}
      activeOpacity={0.8}
    >
      <Text style={[styles.label, disabled && styles.labelDisabled]}>
        {label}
      </Text>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  btn: { backgroundColor: '#4f86f7', padding: 16, borderRadius: 10, alignItems: 'center' },
  btnDisabled: { backgroundColor: '#c4d8ff', opacity: 0.7 },
  label: { color: '#fff', fontWeight: 'bold', fontSize: 16 },
  labelDisabled: { color: 'rgba(255,255,255,0.6)' },
});

TouchableHighlight for Background Color Change

TouchableHighlight changes the background color of the element when pressed, instead of fading opacity like TouchableOpacity. Set underlayColor to the color that appears on press. It must have exactly one child — wrap multiple children in a View. TouchableHighlight is useful for list items and menu rows where a color flash is the expected native behavior (iOS table view style). It is less common in custom UI where designers specify opacity-based feedback.

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

export default function ListItem({ title, subtitle, onPress }) {
  return (
    <TouchableHighlight
      onPress={onPress}
      underlayColor='#f0f0f0' // highlight color when pressed
      activeOpacity={1}        // no opacity change, only highlight
    >
      <View style={styles.row}>
        <Text style={styles.title}>{title}</Text>
        <Text style={styles.subtitle}>{subtitle}</Text>
      </View>
    </TouchableHighlight>
  );
}

const styles = StyleSheet.create({
  row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 14, backgroundColor: '#fff', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee' },
  title: { fontSize: 16, color: '#222' },
  subtitle: { fontSize: 14, color: '#999' },
});

Preventing Ghost Clicks in ScrollView

Inside a ScrollView or FlatList, tapping a Pressable or TouchableOpacity can trigger the onPress when the user intended to scroll. The keyboardShouldPersistTaps='handled' prop on the scroll container helps, and Pressable's unstable_pressDelay adds a delay before recognizing a press. For lists, setting delayPressIn on TouchableOpacity gives the scroll handler priority. Understanding touch gesture priority helps you build lists that scroll smoothly without accidentally triggering row taps during scroll.

import { FlatList, TouchableOpacity, Text, View, StyleSheet } from 'react-native';

export default function ScrollableList({ items }) {
  return (
    <FlatList
      data={items}
      keyExtractor={item => item.id}
      renderItem={({ item }) => (
        <TouchableOpacity
          onPress={() => console.log('Item pressed:', item.title)}
          delayPressIn={50}  // give scroll a 50ms head start
        >
          <View style={styles.row}>
            <Text style={styles.title}>{item.title}</Text>
          </View>
        </TouchableOpacity>
      )}
      keyboardShouldPersistTaps='handled'
    />
  );
}

const styles = StyleSheet.create({
  row: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee' },
  title: { fontSize: 16 },
});

Scale Animation on Press with Pressable

Combine Pressable with the Animated API to create a scale effect on press — a popular design pattern where the button slightly shrinks when held. This gives physical depth to flat UI elements. Use Animated.spring triggered by onPressIn and onPressOut. While the full Animated API is covered in a later course, this quick pattern demonstrates Pressable's onPressIn/Out events working alongside a visual animation to produce polished, satisfying button feedback.

import { Pressable, Animated, Text, StyleSheet } from 'react-native';
import { useRef } from 'react';

export default function SpringButton({ label, onPress }) {
  const scale = useRef(new Animated.Value(1)).current;

  function pressIn() {
    Animated.spring(scale, { toValue: 0.93, useNativeDriver: true, speed: 50 }).start();
  }
  function pressOut() {
    Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 50 }).start();
  }

  return (
    <Pressable onPress={onPress} onPressIn={pressIn} onPressOut={pressOut}>
      <Animated.View style={[styles.btn, { transform: [{ scale }] }]}>
        <Text style={styles.label}>{label}</Text>
      </Animated.View>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  btn: { backgroundColor: '#4f86f7', padding: 18, borderRadius: 14, alignItems: 'center' },
  label: { color: '#fff', fontWeight: '700', fontSize: 17 },
});

Best Practices for Touchable Components

Follow these best practices: always provide visual feedback on press (opacity, color change, or scale — never use TouchableWithoutFeedback for interactive elements). Ensure minimum tap target size of at least 44×44 points. Use accessibilityRole='button' on custom touchable elements so screen readers announce them correctly. Avoid deeply nesting touchables — nested touchables cause gesture conflicts; use stopPropagation patterns or restructure the layout. Prefer Pressable for new code — it's more powerful and the React Native team's recommended choice going forward.

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

// Best-practice accessible button
export default function AccessibleButton({ label, onPress, disabled }) {
  return (
    <Pressable
      onPress={onPress}
      disabled={disabled}
      accessibilityRole='button'
      accessibilityState={{ disabled }}
      accessibilityLabel={label}
      hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
      style={({ pressed }) => [
        styles.btn,
        pressed && styles.pressed,
        disabled && styles.disabled,
      ]}
    >
      <Text style={styles.label}>{label}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  btn: { minWidth: 44, minHeight: 44, backgroundColor: '#4f86f7', paddingHorizontal: 20, borderRadius: 10, alignItems: 'center', justifyContent: 'center' },
  pressed: { opacity: 0.75, transform: [{ scale: 0.97 }] },
  disabled: { backgroundColor: '#aac4f7' },
  label: { color: '#fff', fontWeight: '700' },
});

Icon Buttons and FABs

A Floating Action Button (FAB) is an absolutely positioned, circular button that appears above all content to trigger the primary action on a screen. Use position: 'absolute', bottom, and right (or end for RTL support) to anchor it to the bottom-right of the screen. Wrap the circle in a Pressable or TouchableOpacity with a shadow/elevation for depth. Icon buttons (like a back arrow, close X, or filter icon) follow the same 44×44 minimum tap target rule using hitSlop when the visual size is smaller.

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

export default function FAB({ onPress, icon = '+' }) {
  return (
    <TouchableOpacity
      style={styles.fab}
      onPress={onPress}
      activeOpacity={0.85}
      accessibilityRole='button'
      accessibilityLabel='Add new item'
    >
      <Text style={styles.icon}>{icon}</Text>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  fab: {
    position: 'absolute',
    bottom: 24,
    right: 24,
    width: 60,
    height: 60,
    borderRadius: 30,
    backgroundColor: '#4f86f7',
    alignItems: 'center',
    justifyContent: 'center',
    // Shadow iOS
    shadowColor: '#4f86f7',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.4,
    shadowRadius: 8,
    // Shadow Android
    elevation: 8,
  },
  icon: { color: '#fff', fontSize: 28, fontWeight: '300', lineHeight: 32 },
});

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: TouchableOpacity provides opacity-based press feedback and is widely used in existing codebases, Pressable is the modern API with function-based dynamic styles and more precise event control, and hitSlop expands the tap target area for small interactive elements. Next up we explore toggles, switches, and checkboxes.

免费开始

用 AI 导师学习 JavaScript — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
30
课程
120

常见问题解答

「使用 TouchableOpacity 与 Pressable 处理点击」课时是免费的吗?

是的 — 「使用 TouchableOpacity 与 Pressable 处理点击」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Native Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Native Academy 课程共包含 4 节课。

「使用 TouchableOpacity 与 Pressable 处理点击」这节课中我会学到什么?

使用 TouchableOpacity 和较新的 Pressable API 为界面元素添加点击反馈,包括按下和松开状态的视觉反馈。 你通过在浏览器中直接运行的动手代码来练习 React Native Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Native Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Native Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「使用 TouchableOpacity 与 Pressable 处理点击」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Native Academy 课中编写并运行代码吗?

能。每节 React Native Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. TextInput 基础与键盘类型
  2. 使用 TouchableOpacity 与 Pressable 处理点击
  3. 切换按钮、开关与复选框
  4. 构建简单的登录表单
← 返回 React Native Academy