英文:
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:
<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.
答案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(() => {
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();
}
});
// this fixes everything!
return () => {
animation.reset();
};
}, [message]);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论