Files
digital-pilates/components/AnimatedNumber.tsx

89 lines
2.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useRef, useState } from 'react';
import { Animated, Easing, TextStyle } from 'react-native';
type AnimatedNumberProps = {
value: number; // 最终值
durationMs?: number;
format?: (v: number) => string;
style?: TextStyle;
/** 当该值变化时从0重新动画 */
resetToken?: unknown;
};
export function AnimatedNumber({
value,
durationMs = 300,
format,
style,
resetToken,
}: AnimatedNumberProps) {
const opacity = useRef(new Animated.Value(1)).current;
const [display, setDisplay] = useState<string>(() =>
format ? format(value) : `${Math.round(value)}`
);
const [currentValue, setCurrentValue] = useState(value);
const [lastResetToken, setLastResetToken] = useState(resetToken);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
// 检查是否需要触发动画
const valueChanged = value !== currentValue;
const resetTokenChanged = resetToken !== lastResetToken;
// 如果值没有变化且resetToken也没有变化或者正在动画中则不执行新动画
if ((!valueChanged && !resetTokenChanged) || isAnimating) {
return;
}
// 标记开始动画
setIsAnimating(true);
// 停止当前动画
opacity.stopAnimation(() => {
// 创建优雅的透明度变化动画
const fadeOut = Animated.timing(opacity, {
toValue: 0.2, // 淡出到较低透明度
duration: durationMs * 0.4, // 淡出占总时长的40%
easing: Easing.out(Easing.quad),
useNativeDriver: false,
});
const fadeIn = Animated.timing(opacity, {
toValue: 1,
duration: durationMs * 0.6, // 淡入占总时长的60%
easing: Easing.out(Easing.quad),
useNativeDriver: false,
});
// 在淡出完成时更新数字显示
fadeOut.start(() => {
// 更新当前值和显示
setCurrentValue(value);
setLastResetToken(resetToken);
setDisplay(format ? format(value) : `${Math.round(value)}`);
// 然后淡入新数字
fadeIn.start(() => {
// 动画完成,标记结束
setIsAnimating(false);
});
});
});
}, [value, resetToken, currentValue, lastResetToken, isAnimating, durationMs, format]);
return (
<Animated.Text
style={[
style,
{
opacity: opacity
}
]}
>
{display}
</Animated.Text>
);
}