React Native Animated.sequence. 调用停止后,开始方法中的回调不会立即触发。

huangapple go评论68阅读模式
英文:

React native Animated.sequence. Callback in start method not triggered immediately after calling stop

问题

I have a parent component that triggers a toast upon successfully completing an action (copy text in a list). The toast animates in and out (fade and scale) over 2700ms after the text is copied to the clipboard.

The problem I have is that if another item is copied from the parent whilst the animation is in progress, I want to stop the animation and trigger a new toast.

Parent component has a series of CopyableItems:

<CopyableItem value='Item 1' sideValue='Text to be copied' onPress={onInfoCopied} />
const onInfoCopied = (value) => {
    if (value === message) {
        // don't do anything, same item pressed again
    } else {
        setMessage(value);
        setShowToast(true);
    }
};

...and renders the Toast like this:

{message && (
    <Toast message={`${message} copied`} toastVisible={showToast} endAnimation={() => animationEnded()} />
)}
const animationEnded = () => {
    console.log('animation Ended, toast false');
    setShowToast(false);
    setMessage('');
};

Toast component:

useEffect(() => {
    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,
        }),
    ]);
    if (animationRunning.current) {
        // Why doesn't this trigger the callback in start() below and call endAnimation() immediately!
        animation.stop();
        animation.reset();
    } else {
        animationRunning.current = true;
    }
    animation.start(() => {
        animationRunning.current = false;
        endAnimation();
    });
}, [message]);

My expectation is that the callback in the start() method should be called immediately after the animation.stop() call (when copying another item during a running toast animation)- but it appears to wait until the original animation timing sequence is finished before calling it (2700ms after animation start).

This results in another animation being triggered but the stop callback from the first animation is triggered during the second animation, hiding the second animation early (via the showToast state change in the parent).

Am I making some kind of stupid error here, and if not, does anyone have a better solution for this? ie. Stopping an animation and re-triggering a new.

英文:

I have a parent component that triggers a toast upon successfully completing an action (copy text in a list) The toast animates in and out (fade and scale) over 2700ms after the text is copied to the clipboard.

The problem I have is that if another item is copied from the parent whilst the animation is in progress, I want to stop the animation and trigger a new toast.

Parent component has a series of CopyableItems:

&lt;CopyableItem value=&#39;Item 1&#39; sideValue=&#39;Text to be copied&#39; onPress={onInfoCopied} /&gt;


const onInfoCopied = (value) =&gt; {
	if (value === message) {
		// don&#39;t do anything, same item pressed again
	} else {
		setMessage(value);
		setShowToast(true);
	}
};

...and renders the Toast like this:

{message &amp;&amp; (&lt;Toast message={`${message} copied`} toastVisible={showToast} endAnimation={() =&gt; animationEnded()} /&gt;)}

const animationEnded = () =&gt; {
	console.log(&#39;animation Ended, toast false&#39;);
	setShowToast(false);
	setMessage(&#39;&#39;);
};

Toast component:

useEffect(() =&gt; {
	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,
		}),
	]);
	if (animationRunning.current) {
        // Why doesn&#39;t this trigger the callback in start() below and call endAnimation() immediately!
		animation.stop();
		animation.reset();
	} else {
		animationRunning.current = true;
	}
	animation.start(() =&gt; {
		animationRunning.current = false;
		endAnimation();
	});
}, [message]);

My expectation is that the callback in the start() method should be called immediately after the animation.stop() call (when copying another item during a running toast animation)- but it appears to wait until the original animation timing sequence is finished before calling it (2700ms after animation start).

This results in another animation being triggered but the stop callback from the first animation is triggered during the second animation, hiding the second animation early (via the showToast state change in the parent).

Am I making some kind of stupid error here and if not does anyone have a better solution for this? ie. Stopping an animation and re-triggering a new.

答案1

得分: 0

对于其他人遇到相同问题的人,解决方案是删除条件动画停止(animation.stop())和动画重置(animation.reset()),并在 useEffect 钩子中添加一个清理函数,该函数调用 animation.reset()。通过在动画的 animation.start 回调中添加一个检查'finished',只有当动画顺利完成而没有中断时才调用 endAnimation():

useEffect(() => {
    const animation = Animated.sequence([
        Animated.parallel([
            Animated.timing(fadeAnim, {
                duration: 300,
                easing: Easing.out(Easing.quad),
                toValue: 1.05,
                useNativeDriver: true,
            }),
            Animated.timing(scaleAnim, {
                duration: 250,
                easing: Easing.inOut(Easing.linear),
                toValue: 1.1,
                useNativeDriver: true,
            }),
        ]),
        Animated.timing(scaleAnim, {
            duration: 250,
            easing: Easing.inOut(Easing.linear),
            toValue: 1,
            useNativeDriver: true,
        }),
        Animated.delay(duration),
        Animated.timing(fadeAnim, {
            duration: 300,
            easing: Easing.out(Easing.linear),
            toValue: 0,
            useNativeDriver: true,
        }),
    ]);

    animation.start(({ finished }) => {
        if (finished) {
            endAnimation();
        }
    });

    // 这个修复了一切!
    return () => {
        animation.reset();
    };
}, [message]);
英文:

For anyone else having the same problem, the solution was to remove the conditional animation.stop() and animation.reset() and add a cleanup function in the useEffect hook that calls animation.reset() instead. By adding a check for 'finished' in the animation.start callback which is only set to true if the animation finishes without interruptions I was able to only call endAnimation() only when the animation properly finished:

useEffect(() =&gt; {
	const animation = Animated.sequence([
		Animated.parallel([
			Animated.timing(fadeAnim, {
				duration: 300,
				easing: Easing.out(Easing.quad),
				toValue: 1.05,
				useNativeDriver: true,
			}),
			Animated.timing(scaleAnim, {
				duration: 250,
				easing: Easing.inOut(Easing.linear),
				toValue: 1.1,
				useNativeDriver: true,
			}),
		]),
		Animated.timing(scaleAnim, {
			duration: 250,
			easing: Easing.inOut(Easing.linear),
			toValue: 1,
			useNativeDriver: true,
		}),
		Animated.delay(duration),
		Animated.timing(fadeAnim, {
			duration: 300,
			easing: Easing.out(Easing.linear),
			toValue: 0,
			useNativeDriver: true,
		}),
	]);

	animation.start(({ finished }) =&gt; {
		if (finished) {
			endAnimation();
		}
	});
    // this fixes everything!
	return () =&gt; {
		animation.reset();
	};
}, [message]);

huangapple
  • 本文由 发表于 2023年7月6日 16:16:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76626817.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定