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(() => 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 ( {display} ); }