React Native DrawerLayout:子视图动画引发无限递归问题
给Expo GestureHandler.DrawerLayout的主视图添加动画
我最近做项目时碰到了个需求:Expo的GestureHandler.DrawerLayout默认只支持给抽屉内容(也就是renderNavigationView回调里传入的AnimatedInterpolation值)加动画,但我想让主视图跟着抽屉的滑动同步产生动画效果。
最开始我想的是,在renderNavigationView里拿到抽屉的滑动进度动画值,把它存到组件状态里,然后在主视图的render逻辑里用这个状态值来控制动画。我整理了一个最小示例的核心代码:
import React, { useState } from 'react'; import { View, Animated, StyleSheet } from 'react-native'; import { DrawerLayout } from '@react-native-gesture-handler'; export default function App() { const [drawerProgress, setDrawerProgress] = useState(new Animated.Value(0)); const renderDrawerContent = (progress) => { // 将抽屉的动画进度存入组件状态 setDrawerProgress(progress); return ( <View style={styles.drawerContainer}> {/* 这里放你的抽屉内部内容 */} </View> ); }; // 根据抽屉进度计算主视图的缩放比例 const mainViewScale = drawerProgress.interpolate({ inputRange: [0, 1], outputRange: [1, 0.88], }); // 计算主视图的横向位移 const mainViewTranslateX = drawerProgress.interpolate({ inputRange: [0, 1], outputRange: [0, 180], }); return ( <DrawerLayout renderNavigationView={renderDrawerContent} drawerWidth={300} style={styles.rootContainer} > <Animated.View style={[ styles.mainContent, { transform: [ { scale: mainViewScale }, { translateX: mainViewTranslateX }, ], }, ]} > {/* 主视图的内容区域 */} </Animated.View> </DrawerLayout> ); } const styles = StyleSheet.create({ rootContainer: { flex: 1, }, drawerContainer: { flex: 1, backgroundColor: '#ffffff', padding: 16, }, mainContent: { flex: 1, backgroundColor: '#f0f0f0', justifyContent: 'center', alignItems: 'center', }, });
不过实际测试后发现,直接把Animated.Value存入useState会有小问题——因为Animated的值是可变的,状态更新可能无法正确触发组件的重渲染,导致动画不够流畅。后来我改成用useRef来保存这个动画值,再通过Animated.timing同步进度,效果就顺畅多了:
import React, { useRef } from 'react'; import { View, Animated, StyleSheet } from 'react-native'; import { DrawerLayout } from '@react-native-gesture-handler'; export default function App() { // 用useRef保存动画值,避免状态更新的额外开销 const drawerProgress = useRef(new Animated.Value(0)).current; const renderDrawerContent = (progress) => { // 同步抽屉的动画进度到我们的变量 Animated.timing(drawerProgress, { toValue: progress, duration: 0, useNativeDriver: false, }).start(); return ( <View style={styles.drawerContainer}> {/* 抽屉内容 */} </View> ); }; const mainViewScale = drawerProgress.interpolate({ inputRange: [0, 1], outputRange: [1, 0.88], }); const mainViewTranslateX = drawerProgress.interpolate({ inputRange: [0, 1], outputRange: [0, 180], }); return ( <DrawerLayout renderNavigationView={renderDrawerContent} drawerWidth={300} style={styles.rootContainer} > <Animated.View style={[ styles.mainContent, { transform: [ { scale: mainViewScale }, { translateX: mainViewTranslateX }, ], }, ]} > {/* 主视图内容 */} </Animated.View> </DrawerLayout> ); } // 样式部分和之前一致 const styles = StyleSheet.create({ rootContainer: { flex: 1, }, drawerContainer: { flex: 1, backgroundColor: '#ffffff', padding: 16, }, mainContent: { flex: 1, backgroundColor: '#f0f0f0', justifyContent: 'center', alignItems: 'center', }, });
这样调整后,主视图就能跟着抽屉的滑动,平滑地实现缩放和位移的动画效果了。核心就是利用抽屉滑动时提供的progress动画值,将其映射到主视图的样式变换上。
内容的提问来源于stack exchange,提问作者Brett Higgins
相关产品推荐
相关产品推荐

