import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Animated, InteractionManager, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'; import { fetchHourlyStepSamples, fetchStepCount, HourlyStepData } from '@/utils/health'; import { logger } from '@/utils/logger'; import dayjs from 'dayjs'; import { Image } from 'expo-image'; import { useRouter } from 'expo-router'; import { AnimatedNumber } from './AnimatedNumber'; // 使用原生View来替代SVG,避免导入问题 // import Svg, { Rect } from 'react-native-svg'; interface StepsCardProps { curDate: Date stepGoal: number; style?: ViewStyle; } const StepsCard: React.FC = ({ curDate, style, }) => { const router = useRouter(); const [stepCount, setStepCount] = useState(0) const [hourlySteps, setHourSteps] = useState([]) const getStepData = useCallback(async (date: Date) => { try { logger.info('获取步数数据...'); // 先获取步数,立即更新UI const [steps, hourly] = await Promise.all([ fetchStepCount(date), fetchHourlyStepSamples(date) ]); setStepCount(steps); setHourSteps(hourly); } catch (error) { logger.error('获取步数数据失败:', error); } }, []); useEffect(() => { if (curDate) { getStepData(curDate); } }, [curDate]); // 优化:减少动画值数量,只为有数据的小时创建动画 const animatedValues = useRef>(new Map()).current; // 优化:简化柱状图数据计算,减少计算量 const chartData = useMemo(() => { if (!hourlySteps || hourlySteps.length === 0) { return Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0, height: 0 })); } // 优化:只计算有数据的小时的最大步数 const activeSteps = hourlySteps.filter(data => data.steps > 0); if (activeSteps.length === 0) { return Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0, height: 0 })); } const maxSteps = Math.max(...activeSteps.map(data => data.steps)); const maxHeight = 20; return hourlySteps.map(data => ({ ...data, height: data.steps > 0 ? (data.steps / maxSteps) * maxHeight : 0 })); }, [hourlySteps]); // 获取当前小时 const currentHour = new Date().getHours(); // 优化:延迟执行动画,减少UI阻塞 useEffect(() => { const hasData = chartData && chartData.length > 0 && chartData.some(data => data.steps > 0); if (hasData) { // 使用 InteractionManager 确保动画不会阻塞用户交互 InteractionManager.runAfterInteractions(() => { // 只为有数据的小时创建和执行动画 chartData.forEach((data, index) => { if (data.steps > 0) { // 懒创建动画值 if (!animatedValues.has(index)) { animatedValues.set(index, new Animated.Value(0)); } const animValue = animatedValues.get(index)!; animValue.setValue(0); // 使用更高性能的timing动画替代spring Animated.timing(animValue, { toValue: 1, duration: 300, useNativeDriver: false, }).start(); } }); }); } }, [chartData, animatedValues]); const CardContent = () => ( <> {/* 标题和步数显示 */} 步数 {/* 柱状图 */} {chartData.map((data, index) => { // 判断是否是当前小时或者有活动的小时 const isActive = data.steps > 0; const isCurrent = index <= currentHour; // 优化:只为有数据的柱体创建动画插值 const animValue = animatedValues.get(index); let animatedScale: Animated.AnimatedInterpolation | undefined; let animatedOpacity: Animated.AnimatedInterpolation | undefined; if (animValue && isActive) { animatedScale = animValue.interpolate({ inputRange: [0, 1], outputRange: [0, 1], }); animatedOpacity = animValue.interpolate({ inputRange: [0, 1], outputRange: [0, 1], }); } return ( {/* 背景柱体 - 始终显示,使用相似色系的淡色 */} {/* 数据柱体 - 只有当有数据时才显示并执行动画 */} {isActive && ( )} ); })} {/* 步数和目标显示 */} stepCount !== null ? `${Math.round(v)}` : '——'} resetToken={stepCount} /> ); return ( { // 传递当前日期参数到详情页 const dateParam = dayjs(curDate).format('YYYY-MM-DD'); router.push(`/steps/detail?date=${dateParam}`); }} activeOpacity={0.8} > ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'space-between', borderRadius: 20, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.08, shadowRadius: 20, elevation: 8, }, header: { flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', }, titleIcon: { width: 16, height: 16, marginRight: 6, resizeMode: 'contain', }, title: { fontSize: 14, color: '#192126', fontWeight: '600' }, footprintIcons: { flexDirection: 'row', alignItems: 'center', gap: 6, }, chartContainer: { flex: 1, justifyContent: 'center', marginTop: 6 }, chartWrapper: { width: '100%', alignItems: 'center', }, chartArea: { flexDirection: 'row', alignItems: 'flex-end', height: 20, width: '100%', maxWidth: 240, justifyContent: 'space-between', paddingHorizontal: 4, }, barContainer: { width: 4, height: 20, alignItems: 'center', justifyContent: 'flex-end', position: 'relative', }, chartBar: { width: 4, borderRadius: 1, position: 'absolute', bottom: 0, }, statsContainer: { alignItems: 'flex-start', marginTop: 6 }, stepCount: { fontSize: 18, fontWeight: '600', color: '#192126', }, }); export default StepsCard;