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

React Native自定义BottomSheet:背景变暗与点击外部收起实现方案

解决方案

针对你提出的两个需求,我们可以通过在BottomSheet组件中添加背景遮罩层来同时解决,以下是修改后的代码和关键说明:

修改后的BottomSheet组件代码 (bottomsheet.js)

import { Dimensions, StyleSheet, Text, View, TouchableOpacity } from 'react-native'
import React, { useCallback, useImperativeHandle } from 'react'
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { interpolate, useAnimatedStyle, useSharedValue, withSpring, createAnimatedComponent } from 'react-native-reanimated';

const AnimatedTouchableOpacity = createAnimatedComponent(TouchableOpacity);

const {height: SCREEN_HEIGHT} = Dimensions.get('window');
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50;

const BottomSheet = React.forwardRef(({},ref) => {
  const translateY = useSharedValue(35);
  const active = useSharedValue(false);

  const scrollTo = useCallback ((destination = Number) =>{
    'worklet';
    active.value = destination !== 35;
    translateY.value = withSpring(destination, {damping:15});
  }, []);

  const isActive1 = useCallback (()=>{
    return active.value;
  },[])

  useImperativeHandle(ref, () => ({scrollTo, isActive1}), [scrollTo, isActive1]);

  const updatePan = useSharedValue({y:0});

  const activateGesture = Gesture.Pan()
    .onStart(() => {
      updatePan.value = { y:translateY.value};
    })
    .onUpdate((e) => {
      translateY.value = e.translationY + updatePan.value.y;
      translateY.value = Math.max(translateY.value, MAX_TRANSLATE_Y);
    })
    .onEnd (() => {
      if (translateY.value > -SCREEN_HEIGHT/3){
        scrollTo(35);
      } else if (translateY.value < -SCREEN_HEIGHT / 1.5) {
        scrollTo(MAX_TRANSLATE_Y);
      }
    });

  const rBottomSheetStyle = useAnimatedStyle (() => {
    const borderRadius = interpolate(
      translateY.value, 
      [MAX_TRANSLATE_Y + 100, MAX_TRANSLATE_Y], 
      [25, 5], 
      Extrapolate.CLAMP
    );
    return {
      borderRadius,
      transform: [{ translateY: translateY.value }],
    };
  });

  // 背景遮罩的动画样式
  const rBackdropStyle = useAnimatedStyle(() => {
    const opacity = interpolate(
      translateY.value,
      [35, -SCREEN_HEIGHT/3],
      [0, 0.5],
      Extrapolate.CLAMP
    );
    return { opacity };
  });

  return (
    <>
      {/* 全屏背景遮罩 */}
      <AnimatedTouchableOpacity
        style={[styles.backdrop, rBackdropStyle]}
        onPress={() => scrollTo(35)}
        activeOpacity={1}
        disabled={!active.value}
      />
      <GestureDetector gesture={activateGesture}>
        <Animated.View 
          style={[styles.bottomSheetContainer, rBottomSheetStyle]} 
        >
          <View style={styles.line} />
        </Animated.View>
      </GestureDetector>
    </>
  )
})

const styles = StyleSheet.create({
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: '#000',
    zIndex: 99,
  },
  bottomSheetContainer: {
    height: SCREEN_HEIGHT, 
    width:'100%', 
    backgroundColor: 'white',
    position: 'absolute',
    top: SCREEN_HEIGHT,
    borderRadius: 25,
    zIndex: 100,
  },
  line:{
    width: 75,
    backgroundColor: 'grey',
    height: 4,
    alignSelf: 'center',
    marginVertical: 15,
    borderRadius:2,
  }
})

export default BottomSheet

关键修改说明

  1. 背景变暗实现

    • 添加了全屏的黑色遮罩层,通过useAnimatedStyle根据BottomSheet的translateY值动态调整透明度:当BottomSheet收起(translateY=35)时透明度为0,展开到一定位置后透明度变为0.5,实现背景渐变变暗的效果。
    • 设置遮罩层的zIndex为99,BottomSheet容器的zIndex为100,确保BottomSheet始终在遮罩上方。
  2. 点击空白收起BottomSheet

    • 将遮罩层设置为可点击的AnimatedTouchableOpacity,绑定onPress事件,点击时调用scrollTo(35)触发BottomSheet收起逻辑。
    • 添加disabled={!active.value},只有当BottomSheet处于激活状态时才响应点击,避免收起状态下的误触。

可选优化(App.js)

为了符合React Hooks规范,建议给onPress函数添加依赖数组:

const onPress = useCallback (() => {
  const isActive = firstRef?.current?.isActive1();
  if (isActive) {
    firstRef?.current?.scrollTo(35);
  } else {
    firstRef?.current?.scrollTo(-200);
  }
}, [firstRef])

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 12:54:26