You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让React Native Modal占屏幕50%且保留缩放动画与下拉关闭

React Native 50%高度Modal实现方案

核心思路

放弃通过transparent属性强行控制Modal高度,改为在Modal内部嵌套自定义容器,单独设置容器高度为屏幕的50%,同时通过Animated和PanResponder实现缩放动画与下拉关闭功能。

具体实现步骤

  • 容器样式控制:Modal设置transparent={true}并添加半透明遮罩,内部容器固定高度为Dimensions.get('window').height * 0.5,底部对齐屏幕。
  • 缩放动画实现:用Animated.spring在Modal显示时,将容器的scale从0.95过渡到1,配合弹性动画增强自然感。
  • 下拉关闭逻辑:通过PanResponder监听容器拖拽手势,当向下拖拽距离超过容器高度1/3时,触发Modal关闭并伴随位移动画;不足阈值则回弹原位。

完整代码示例

import React, { useState, useRef, useEffect } from 'react';
import { View, Modal, StyleSheet, Dimensions, Animated, PanResponder, TouchableWithoutFeedback, Text } from 'react-native';

const { height: screenHeight } = Dimensions.get('window');
const MODAL_HEIGHT = screenHeight * 0.5;

const HalfHeightModal = () => {
  const [modalVisible, setModalVisible] = useState(false);
  const scaleAnim = useRef(new Animated.Value(0.95)).current;
  const translateYAnim = useRef(new Animated.Value(0)).current;
  
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (_, gestureState) => {
        if (gestureState.dy > 0) {
          translateYAnim.setValue(gestureState.dy);
        }
      },
      onPanResponderRelease: (_, gestureState) => {
        if (gestureState.dy > MODAL_HEIGHT / 3) {
          Animated.timing(translateYAnim, {
            toValue: MODAL_HEIGHT,
            duration: 200,
            useNativeDriver: true,
          }).start(() => {
            setModalVisible(false);
            translateYAnim.setValue(0);
          });
        } else {
          Animated.spring(translateYAnim, {
            toValue: 0,
            useNativeDriver: true,
            bounciness: 5,
          }).start();
        }
      },
    })
  ).current;

  useEffect(() => {
    if (modalVisible) {
      Animated.spring(scaleAnim, {
        toValue: 1,
        useNativeDriver: true,
        bounciness: 5,
      }).start();
    } else {
      scaleAnim.setValue(0.95);
    }
  }, [modalVisible, scaleAnim]);

  return (
    <View style={styles.container}>
      <TouchableWithoutFeedback onPress={() => setModalVisible(true)}>
        <View style={styles.openButton}>
          <Text style={styles.buttonText}>打开50%高度Modal</Text>
        </View>
      </TouchableWithoutFeedback>

      <Modal
        visible={modalVisible}
        animationType="none"
        transparent={true}
        onRequestClose={() => setModalVisible(false)}
      >
        <TouchableWithoutFeedback onPress={() => setModalVisible(false)}>
          <View style={styles.modalOverlay} />
        </TouchableWithoutFeedback>

        <Animated.View
          style={[
            styles.modalContainer,
            {
              transform: [
                { scale: scaleAnim },
                { translateY: translateYAnim },
              ],
            },
          ]}
          {...panResponder.panHandlers}
        >
          <View style={styles.modalContent}>
            <Text style={styles.contentText}>这是高度为屏幕50%的Modal</Text>
            <Text style={styles.tipText}>向下拖拽可关闭</Text>
          </View>
        </Animated.View>
      </Modal>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
  },
  openButton: {
    width: 220,
    height: 56,
    backgroundColor: '#2196F3',
    borderRadius: 12,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  modalOverlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  modalContainer: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    height: MODAL_HEIGHT,
    backgroundColor: '#fff',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: -3 },
    shadowOpacity: 0.3,
    shadowRadius: 6,
    elevation: 8,
  },
  modalContent: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
    alignItems: 'center',
  },
  contentText: {
    fontSize: 18,
    fontWeight: '600',
    marginBottom: 12,
  },
  tipText: {
    fontSize: 14,
    color: '#666',
  },
});

export default HalfHeightModal;

关键说明

  • 禁用Modal默认动画,改用自定义Animated动画,规避transparent属性导致的原生动画失效问题。
  • 半透明遮罩独立设置,点击遮罩或下拉容器均可关闭Modal,覆盖两种常用交互场景。
  • 容器固定在屏幕底部,高度严格为屏幕的50%,动画流畅度和交互逻辑完全匹配需求。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 06:43:26