Expo React Native SVG:自定义汉堡菜单 onPress 动画实现求助
解决React Native汉堡菜单转叉号的动画实现问题
嘿,我明白你遇到的问题了——用setNativeProps搞汉堡菜单转叉号的动画确实容易踩坑,尤其是在onPress里控制序列动画的时候。我给你分享一个用官方Animated库实现的靠谱方案,能完美实现你要的效果:先收缩中间线条,再让上下线条移动形成叉号。
为什么setNativeProps不适合这个场景?
setNativeProps是直接操作原生视图属性,跳过React的状态更新流程,虽然性能好,但很难精准控制**动画序列(先收缩中间线,再移动上下线)**的执行顺序- 在
onPress中直接调用setNativeProps,容易因为React的异步更新机制导致动画状态不同步,出现部分生效的情况
推荐方案:使用React Native Animated库实现
Animated是官方推荐的动画库,能完美处理序列动画、并行动画,还能和React状态联动,下面是完整的实现代码:
1. 基础组件结构与状态定义
首先导入需要的模块,定义状态和动画值:
import React, { useState, useRef } from 'react'; import { View, TouchableOpacity, Animated } from 'react-native'; const HamburgerMenu = () => { // 控制菜单展开/收起状态 const [isOpen, setIsOpen] = useState(false); // 动画值定义:中间线条宽度、上下线条的Y偏移 const middleLineWidth = useRef(new Animated.Value(24)).current; const topLineY = useRef(new Animated.Value(0)).current; const bottomLineY = useRef(new Animated.Value(0)).current; // 线条的基础样式 const lineStyle = { width: 24, height: 2, backgroundColor: '#000', borderRadius: 1, position: 'absolute', left: 0, };
2. 实现 onPress 中的动画逻辑
在点击事件里,根据当前状态执行对应的动画序列:
const handlePress = () => { if (!isOpen) { // 从汉堡菜单转为叉号:先收缩中间线,再移动上下线 Animated.sequence([ // 第一步:中间线条收缩到0 Animated.timing(middleLineWidth, { toValue: 0, duration: 200, useNativeDriver: false, // 因为修改的是width属性,不支持原生驱动 }), // 第二步:同时移动上下线条到目标位置 Animated.parallel([ Animated.timing(topLineY, { toValue: 12, // 对应你要的y2=18,假设初始top是6,偏移量为12 duration: 200, useNativeDriver: false, }), Animated.timing(bottomLineY, { toValue: -12, // 对应你要的y2=6,假设初始top是18,偏移量为-12 duration: 200, useNativeDriver: false, }), ]), ]).start(() => setIsOpen(true)); } else { // 从叉号转回汉堡菜单:先移动上下线回原位,再恢复中间线宽度 Animated.sequence([ Animated.parallel([ Animated.timing(topLineY, { toValue: 0, duration: 200, useNativeDriver: false, }), Animated.timing(bottomLineY, { toValue: 0, duration: 200, useNativeDriver: false, }), ]), Animated.timing(middleLineWidth, { toValue: 24, duration: 200, useNativeDriver: false, }), ]).start(() => setIsOpen(false)); } };
3. 渲染动画视图
把动画值绑定到对应的线条样式上:
return ( <TouchableOpacity onPress={handlePress} style={{ width: 24, height: 20, position: 'relative' }} > {/* 顶部线条 */} <Animated.View style={[ lineStyle, { top: 6, transform: [{ translateY: topLineY }] }, ]} /> {/* 中间线条 */} <Animated.View style={[ lineStyle, { top: 9, width: middleLineWidth }, ]} /> {/* 底部线条 */} <Animated.View style={[ lineStyle, { top: 18, transform: [{ translateY: bottomLineY }] }, ]} /> </TouchableOpacity> ); }; export default HamburgerMenu;
关键细节说明
- 动画序列控制:用
Animated.sequence确保先执行中间线收缩,再执行上下线移动;用Animated.parallel让上下线的移动动画同时进行 - 状态联动:动画完成后再切换
isOpen状态,避免状态和动画不同步 - 样式适配:根据你实际的线条初始位置调整
top值和偏移量,我这里假设三条线的初始top分别是6、9、18,你可以根据自己的布局修改 - useNativeDriver:因为我们修改的是
width和top(通过transform的translateY),其中width不支持原生驱动,所以这里设置为false;如果只做transform的动画可以开启原生驱动提升性能
可选优化:添加回弹效果
如果想让动画更自然,可以把Animated.timing换成Animated.spring,比如:
Animated.spring(topLineY, { toValue: 12, stiffness: 300, damping: 30, useNativeDriver: false, })
这样动画会有轻微的回弹,交互体验更好。
内容的提问来源于stack exchange,提问作者TheBag
相关产品推荐
相关产品推荐

