import React, { useEffect, useMemo, useRef } from 'react'; import { Animated, Easing, StyleSheet, Text, View } from 'react-native'; import Svg, { Circle, G } from 'react-native-svg'; type CircularRingProps = { size?: number; strokeWidth?: number; trackColor?: string; progressColor?: string; progress: number; // 0..1 durationMs?: number; showCenterText?: boolean; /** 当该值变化时,会从0重新动画到 progress */ resetToken?: unknown; /** 进度起始角度(度),默认 -90,使进度从正上方开始 */ startAngleDeg?: number; }; /** * 纯 View 实现的圆环进度条(无依赖),通过两个半圆与旋转实现。 */ export function CircularRing({ size = 120, strokeWidth = 12, trackColor = '#E2D9FD', progressColor = '#8B74F3', progress, durationMs = 900, showCenterText = true, resetToken, startAngleDeg = -90, }: CircularRingProps) { const clamped = useMemo(() => { if (Number.isNaN(progress)) return 0; return Math.min(1, Math.max(0, progress)); }, [progress]); const animated = useRef(new Animated.Value(0)).current; useEffect(() => { // 每次 resetToken 或目标进度变化时,从0动画到 clamped animated.stopAnimation(() => { animated.setValue(0); Animated.timing(animated, { toValue: clamped, duration: durationMs, easing: Easing.out(Easing.cubic), useNativeDriver: false, }).start(); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [clamped, resetToken]); const radius = useMemo(() => (size - strokeWidth) / 2, [size, strokeWidth]); const circumference = useMemo(() => 2 * Math.PI * radius, [radius]); const dashOffset = animated.interpolate({ inputRange: [0, 1], outputRange: [circumference, 0], }); const percentText = useMemo(() => `${Math.round(clamped * 100)}%`, [clamped]); const containerStyle = useMemo(() => [styles.container, { width: size, height: size }], [size]); return ( {/* 轨道 */} {/* 进度 */} {showCenterText && ( {percentText} )} ); } const styles = StyleSheet.create({ container: { alignItems: 'center', justifyContent: 'center', alignSelf: 'center', }, center: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center', }, centerText: { fontSize: 18, fontWeight: '800', color: '#8B74F3', }, }); const AnimatedCircle = Animated.createAnimatedComponent(Circle);