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('0'); const [currentValue, setCurrentValue] = useState(0); useEffect(() => { // 如果值没有变化,不执行动画 if (value === currentValue && resetToken === undefined) { return; } // 停止当前动画 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); setDisplay(format ? format(value) : `${Math.round(value)}`); // 然后淡入新数字 fadeIn.start(); }); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value, resetToken]); // 初始化显示值 useEffect(() => { if (currentValue !== value) { setCurrentValue(value); setDisplay(format ? format(value) : `${Math.round(value)}`); } }, [value, format, currentValue]); return ( {display} ); }