React Native Animated.sequence调用stop后start回调未立即触发问题
问题分析与解决方案
核心问题
你当前的操作错误在于:每次触发useEffect时都会创建新的Animated.sequence实例,调用animation.stop()的是刚创建的新动画对象,而非正在运行的旧动画,导致旧动画完全没被终止,它的回调仍会在原序列结束后执行,进而提前隐藏新Toast。同时animationRunning.current的逻辑存在漏洞,新动画启动后会覆盖标记,但旧动画的回调依然会在结束时修改标记并触发endAnimation。
修正方案1:跟踪并终止运行中的动画实例
把当前正在运行的动画实例存在Ref中,确保每次触发新动画时能精准终止旧动画,同时重置动画状态避免异常:
import { useRef, useEffect } from 'react'; import { Animated } from 'react-native'; const Toast = ({ message, endAnimation, duration = 1500 }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const scaleAnim = useRef(new Animated.Value(1)).current; const currentAnimation = useRef(null); // 保存当前运行的动画实例 useEffect(() => { // 终止旧动画并重置状态 if (currentAnimation.current) { currentAnimation.current.stop(); // 立即重置动画到初始值,避免新动画起始状态异常 Animated.parallel([ Animated.timing(fadeAnim, { toValue: 0, duration: 0, useNativeDriver: true }), Animated.timing(scaleAnim, { toValue: 1, duration: 0, useNativeDriver: true }) ]).start(); } // 创建新动画序列 const animation = Animated.sequence([ Animated.timing(fadeAnim, { duration: 300, toValue: 1, useNativeDriver: true, }), Animated.timing(scaleAnim, { duration: 300, toValue: 1.1, useNativeDriver: true, }), Animated.timing(scaleAnim, { duration: 300, toValue: 1, useNativeDriver: true, }), Animated.delay(duration), Animated.timing(fadeAnim, { duration: 300, toValue: 0, useNativeDriver: true, }), ]); currentAnimation.current = animation; animation.start(({ finished }) => { currentAnimation.current = null; // 仅当动画正常完成时触发回调,避免终止时误执行 if (finished) { endAnimation(); } }); // 组件卸载时清理动画 return () => { if (currentAnimation.current) { currentAnimation.current.stop(); } }; }, [message, duration, endAnimation]); // 你的Toast渲染逻辑 return ( <Animated.View style={{ opacity: fadeAnim, transform: [{ scale: scaleAnim }] }}> <Text>{message}</Text> </Animated.View> ); };
修正方案2:标记有效动画,屏蔽旧回调干扰
如果担心动画终止时的状态同步问题,可以用唯一ID标记当前动画,旧动画的回调触发时直接忽略:
const Toast = ({ message, endAnimation, duration = 1500 }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const scaleAnim = useRef(new Animated.Value(1)).current; const animationId = useRef(0); // 标记当前动画的唯一ID useEffect(() => { const currentId = ++animationId.current; // 重置动画到初始状态 Animated.parallel([ Animated.timing(fadeAnim, { toValue: 0, duration: 0, useNativeDriver: true }), Animated.timing(scaleAnim, { toValue: 1, duration: 0, useNativeDriver: true }) ]).start(() => { const animation = Animated.sequence([ // 动画序列和之前一致 Animated.timing(fadeAnim, { duration: 300, toValue: 1, useNativeDriver: true }), Animated.timing(scaleAnim, { duration: 300, toValue: 1.1, useNativeDriver: true }), Animated.timing(scaleAnim, { duration: 300, toValue: 1, useNativeDriver: true }), Animated.delay(duration), Animated.timing(fadeAnim, { duration: 300, toValue: 0, useNativeDriver: true }), ]); animation.start(() => { // 仅当当前动画是最新的才执行回调 if (animationId.current === currentId) { endAnimation(); } }); }); return () => { animationId.current = 0; // 组件卸载时标记为无效 }; }, [message, duration, endAnimation]); // 渲染逻辑... };
父组件状态简化(可选)
可以合并message和showToast状态,避免状态不同步问题:
const [toastMessage, setToastMessage] = useState(''); const onInfoCopied = (value) => { setToastMessage(value); }; // 渲染Toast {toastMessage && ( <Toast message={`${toastMessage} copied`} endAnimation={() => setToastMessage('')} /> )}
内容的提问来源于stack exchange,提问作者Simple Simon
相关产品推荐
相关产品推荐

