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

React Native自定义抽屉导航动画失效 尺寸跳变 关闭后无法全屏

React Navigation 自定义抽屉主布局动画异常修复

问题现象

实现带开关动画的自定义抽屉导航时,预期抽屉滑动过程中MainLayout会同步执行收缩/展开、圆角变化的平滑过渡,实际出现两个异常:

  • 抽屉手势滑动过程中主布局无实时过渡动画,仅当滑动到全开/全关端点时才直接跳变到最终样式
  • 点击抽屉内置关闭按钮触发关闭时,抽屉收回后主布局无法恢复到全屏无圆角的初始状态

已确认babel配置中已添加react-native-reanimated/plugin插件并清理过缓存,问题仍存在。

相关代码

自定义抽屉组件代码

import 'react-native-gesture-handler';
import React, {useState} from 'react';
import {StyleSheet, TouchableOpacity, Image, Text, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Animated from 'react-native-reanimated';
import {
  createDrawerNavigator,
  DrawerContentScrollView,
  useDrawerProgress,
} from '@react-navigation/drawer';
import {icons} from '../constants';

// screens import
import MainLayout from '../screens/MainLayout';

const Drawer = createDrawerNavigator();

const CustomDrawerItem = ({icon, label}) => {
  return (
    <TouchableOpacity
      style={[styles.drawerItem]}
      onPress={() => console.log('drawer item')}>
      <Image
        source={icon}
        style={styles.drawerItemIcon}
      />
      <Text
        style={styles.drawerItemLabel}>
        {label}
      </Text>
    </TouchableOpacity>
  );
};

const CustomDrawerContent = ({navigation}) => {
  return (
    <DrawerContentScrollView
      scrollEnabled={true}
      contentContainerStyle={{flex: 1}}>
      <View style={{flex: 1, paddingHorizontal: 20}}>
        <View
          style={styles.closeBtnContainer}>
          <TouchableOpacity
            style={styles.closeBtn}
            onPress={() => navigation.closeDrawer()}>
            <Image
              source={icons.close}
              style={styles.closeBtnIcon}
              resizeMode="contain"
            />
          </TouchableOpacity>
        </View>

        <TouchableOpacity
          style={styles.profileButton}
          onPress={() => console.log('profile')}>
          <Image
            source={icons.avatar}
            style={styles.profileImage}
            resizeMode="cover"
          />
          <View style={{marginLeft: 10}}>
            <Text style={styles.commonTxt}>
              John Doe
            </Text>
            <Text style={[styles.commonTxt, {fontSize: 15}]}>
              john@xyz.com
            </Text>
          </View>
        </TouchableOpacity>

        <View style={styles.drawerItmContainer}>
          <CustomDrawerItem
            icon={icons.qr_code}
            label="QR Code"
          />
          <CustomDrawerItem
            icon={icons.resource}
            label="Resource"
          />
          <CustomDrawerItem
            icon={icons.collection_report}
            label="Collection Report"
          />
          <CustomDrawerItem
            icon={icons.change_pin}
            label="Change Pin"
          />
          <View style={styles.separator}/>
        </View>
        <View style={{marginBottom: 20}}>
          <CustomDrawerItem
            icon={icons.logout}
            label="Logout"
          />
        </View>
      </View>
    </DrawerContentScrollView>
  );
};

const CustomDrawer = () => {
  return (
    <LinearGradient
      colors={['#ff9933', '#3b5998', '#192f6a']}
      style={styles.container}>
      <Drawer.Navigator
        screenOptions={{
          headerShown: false,
          drawerType: 'slide',
          overlayColor: 'transparent',
          drawerStyle: {flex: 1, width: '65%', backgroundColor: 'transparent'},
          sceneContainerStyle: {backgroundColor: 'transparent'},
        }}
        drawerContent={props => {
          return <CustomDrawerContent navigation={props.navigation} />;
        }}
        initialRouteName="MainLayout">
        <Drawer.Screen name="MainLayout">
          {props => <MainLayout {...props} />}
        </Drawer.Screen>
      </Drawer.Navigator>
    </LinearGradient>
  );
};

export default CustomDrawer;

const styles = StyleSheet.create({
  drawerItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 20,
    borderRadius: 10,
  },
  drawerItemIcon: {width: 20, height: 20, tintColor: '#fff'},
  drawerItemLabel: {fontSize: 18, color: '#fff', marginLeft: 15},
  profileButton: {flexDirection: 'row', alignItems: 'center', marginTop: 20},
  profileImage: {width: 50, height: 50, borderRadius: 10},
  closeBtnContainer: {alignItems: 'flex-start', justifyContent: 'center'},
  closeBtn: {alignItems: 'center', justifyContent: 'center'},
  closeBtnIcon: {width: 30, height: 30, tintColor: '#fff'},
  commonTxt: {fontSize: 18, color: '#fff'},
  drawerItmContainer: {flex: 1, marginTop: 20},
  separator: {height: 1, backgroundColor: '#fff', marginVertical: 10},
  container: {flex: 1, backgroundColor: '#ff9f1c'},
});

原MainLayout组件代码

import 'react-native-gesture-handler';
import {StyleSheet, Text, View, SafeAreaView} from 'react-native';
import React from 'react';
import Animated from 'react-native-reanimated';
import {useDrawerProgress} from '@react-navigation/drawer';

const MainLayout = props => {
  const progress = useDrawerProgress();

  const scale = Animated.interpolateNode(progress.value, {
    inputRange: [0, 1],
    outputRange: [1, 0.75],
  });
  const borderRadius = Animated.interpolateNode(progress.value, {
    inputRange: [0, 1],
    outputRange: [0, 30],
  });

  const animatedStyle = {
    borderRadius,
    transform: [{scale}],
  };

  return (
    <Animated.View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#fff',
        ...animatedStyle,
      }}>
      <Text>MainLayout</Text>
    </Animated.View>
  );
};

export default MainLayout;

const styles = StyleSheet.create({});

依赖配置(package.json)

{
  "name": "Dhananjaya",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "@react-native-masked-view/masked-view": "^0.2.6",
    "@react-navigation/drawer": "^6.4.1",
    "@react-navigation/native": "^6.0.10",
    "@react-navigation/stack": "^6.2.1",
    "axios": "^0.27.2",
    "native-base": "^3.4.5",
    "react": "17.0.2",
    "react-native": "0.68.2",
    "react-native-gesture-handler": "^2.4.2",
    "react-native-linear-gradient": "^2.5.6",
    "react-native-reanimated": "^2.8.0",
    "react-native-safe-area-context": "^4.2.5",
    "react-native-screens": "^3.13.1",
    "react-native-svg": "^12.3.0"
  },
  "devDependencies": {
    "@babel/core": "7.18.2",
    "@babel/runtime": "7.18.3",
    "@react-native-community/eslint-config": "2.0.0",
    "babel-jest": "26.6.3",
    "eslint": "7.32.0",
    "jest": "26.6.3",
    "metro-react-native-babel-preset": "0.67.0",
    "react-test-renderer": "17.0.2"
  },
  "jest": {
    "preset": "react-native"
  }
}

babel配置(babel.config.js)

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin'],
};

异常效果演示

  • 滑动无实时动画效果:滑动动画异常
  • 点击关闭按钮后主布局不恢复:关闭按钮异常

问题原因

代码中Reanimated v2的API使用方式错误:

  1. useDrawerProgress() 返回的本身就是Reanimated共享值(SharedValue),旧写法直接在渲染阶段取progress.value做插值,无法触发实时的UI更新
  2. interpolateNode是Reanimated v1的旧API,在v2版本中动画样式必须通过useAnimatedStyle钩子定义,才能响应共享值的连续变化,否则只会在值到达端点(0/1)时触发普通重渲染,表现为样式跳变
  3. 点击按钮关闭抽屉时,进度值是连续变化的,但由于样式没有被useAnimatedStyle包裹,无法跟踪中间值的变化,导致最终样式停留在缩放状态无法恢复。

修复方案

替换MainLayout组件代码为以下版本,使用Reanimated v2标准API编写动画样式:

import 'react-native-gesture-handler';
import {StyleSheet, Text, View} from 'react-native';
import React from 'react';
import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import {useDrawerProgress} from '@react-navigation/drawer';

const MainLayout = () => {
  const progress = useDrawerProgress();

  const animatedStyle = useAnimatedStyle(() => {
    // 插值计算缩放值
    const scale = interpolate(progress.value, [0, 1], [1, 0.75]);
    // 插值计算圆角
    const borderRadius = interpolate(progress.value, [0, 1], [0, 30]);
    // 可选:添加X轴偏移,抽屉打开时主布局同步右移,视觉效果更自然
    const translateX = interpolate(progress.value, [0, 1], [0, 60]);

    return {
      borderRadius,
      transform: [{scale}, {translateX}],
      overflow: 'hidden'
    };
  });

  return (
    <Animated.View
      style={[
        {
          flex: 1,
          alignItems: 'center',
          justifyContent: 'center',
          backgroundColor: '#fff',
        },
        animatedStyle
      ]}>
      <Text>MainLayout</Text>
    </Animated.View>
  );
};

export default MainLayout;

const styles = StyleSheet.create({});

修改完成后执行以下命令彻底清理缓存重启服务,确保配置生效:

# 清理metro缓存
npx react-native start --reset-cache
# 安卓重新打包
npx react-native run-android
# iOS重新安装依赖并打包
cd ios && pod install && cd .. && npx react-native run-ios

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 07:54:32