feat: 优化数据加载逻辑,添加应用状态监听以刷新统计数据;为步数卡片添加动画效果
This commit is contained in:
@@ -26,6 +26,7 @@ import dayjs from 'dayjs';
|
|||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
|
AppState,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@@ -240,20 +241,42 @@ export default function ExploreScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载所有数据的统一方法
|
||||||
|
const loadAllData = React.useCallback((targetDate?: Date) => {
|
||||||
|
const dateToUse = targetDate || getCurrentSelectedDate();
|
||||||
|
if (dateToUse) {
|
||||||
|
loadHealthData(dateToUse);
|
||||||
|
if (isLoggedIn) {
|
||||||
|
loadNutritionData(dateToUse);
|
||||||
|
loadMoodData(dateToUse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isLoggedIn]);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
// 聚焦时按当前选中的日期加载,避免与用户手动选择的日期不一致
|
// 每次聚焦时都拉取当前选中日期的最新数据
|
||||||
const currentDate = currentSelectedDate;
|
loadAllData();
|
||||||
if (currentDate) {
|
}, [loadAllData])
|
||||||
loadHealthData(currentDate);
|
|
||||||
if (isLoggedIn) {
|
|
||||||
loadNutritionData(currentDate);
|
|
||||||
loadMoodData(currentDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [selectedIndex])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// AppState 监听:应用从后台返回前台时刷新数据
|
||||||
|
useEffect(() => {
|
||||||
|
const handleAppStateChange = (nextAppState: string) => {
|
||||||
|
if (nextAppState === 'active') {
|
||||||
|
// 应用从后台返回前台,刷新当前选中日期的数据
|
||||||
|
console.log('应用从后台返回前台,刷新统计数据...');
|
||||||
|
loadAllData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription?.remove();
|
||||||
|
};
|
||||||
|
}, [loadAllData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 注册任务
|
// 注册任务
|
||||||
registerTask({
|
registerTask({
|
||||||
@@ -272,11 +295,7 @@ export default function ExploreScreen() {
|
|||||||
// 日期点击时,加载对应日期数据
|
// 日期点击时,加载对应日期数据
|
||||||
const onSelectDate = (index: number, date: Date) => {
|
const onSelectDate = (index: number, date: Date) => {
|
||||||
setSelectedIndex(index);
|
setSelectedIndex(index);
|
||||||
loadHealthData(date);
|
loadAllData(date);
|
||||||
if (isLoggedIn) {
|
|
||||||
loadNutritionData(date);
|
|
||||||
loadMoodData(date);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, useRef, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
View,
|
View,
|
||||||
ViewStyle
|
ViewStyle,
|
||||||
|
Animated
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import { HourlyStepData } from '@/utils/health';
|
import { HourlyStepData } from '@/utils/health';
|
||||||
@@ -23,6 +24,11 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
|||||||
hourlySteps,
|
hourlySteps,
|
||||||
style
|
style
|
||||||
}) => {
|
}) => {
|
||||||
|
// 为每个柱体创建独立的动画值
|
||||||
|
const animatedValues = useRef(
|
||||||
|
Array.from({ length: 24 }, () => new Animated.Value(0))
|
||||||
|
).current;
|
||||||
|
|
||||||
// 计算柱状图数据
|
// 计算柱状图数据
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!hourlySteps || hourlySteps.length === 0) {
|
if (!hourlySteps || hourlySteps.length === 0) {
|
||||||
@@ -42,6 +48,26 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
|||||||
// 获取当前小时
|
// 获取当前小时
|
||||||
const currentHour = new Date().getHours();
|
const currentHour = new Date().getHours();
|
||||||
|
|
||||||
|
// 触发柱体动画
|
||||||
|
useEffect(() => {
|
||||||
|
if (chartData && chartData.length > 0) {
|
||||||
|
// 重置所有动画值
|
||||||
|
animatedValues.forEach(animValue => animValue.setValue(0));
|
||||||
|
|
||||||
|
// 同时启动所有柱体的弹性动画,有步数的柱体才执行动画
|
||||||
|
chartData.forEach((data, index) => {
|
||||||
|
if (data.steps > 0) {
|
||||||
|
Animated.spring(animatedValues[index], {
|
||||||
|
toValue: 1,
|
||||||
|
tension: 150,
|
||||||
|
friction: 8,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [chartData, animatedValues]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
{/* 标题和步数显示 */}
|
{/* 标题和步数显示 */}
|
||||||
@@ -58,14 +84,28 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
|||||||
const isActive = data.steps > 0;
|
const isActive = data.steps > 0;
|
||||||
const isCurrent = index <= currentHour;
|
const isCurrent = index <= currentHour;
|
||||||
|
|
||||||
|
// 动画变换:缩放从0到实际高度
|
||||||
|
const animatedScale = animatedValues[index].interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 动画变换:透明度从0到1
|
||||||
|
const animatedOpacity = animatedValues[index].interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<Animated.View
|
||||||
key={`bar-${index}`}
|
key={`bar-${index}`}
|
||||||
style={[
|
style={[
|
||||||
styles.chartBar,
|
styles.chartBar,
|
||||||
{
|
{
|
||||||
height: data.height || 2, // 最小高度2px
|
height: data.height || 2, // 最小高度2px
|
||||||
backgroundColor: isCurrent && isActive ? '#FFC365' : '#FFEBCB',
|
backgroundColor: isCurrent && isActive ? '#FFC365' : '#FFEBCB',
|
||||||
|
transform: [{ scaleY: animatedScale }],
|
||||||
|
opacity: animatedOpacity,
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user