import React, { useMemo, useRef, useEffect } from 'react'; import { Animated, View, StyleSheet, Easing } from 'react-native'; import Svg, { Circle, G, LinearGradient, Stop, Defs } from 'react-native-svg'; import { ProgressProps } from './interface'; import { usePersistFn } from '../../utils'; import { IconMACheckmark1, IconMACancellation1, } from '../../icons'; import { ShouldRender } from '@xrnjs/ui'; import { renderTextLikeJSX } from '../../core/helpers'; import { isNumber, isArray, isString } from 'lodash'; import { validProgress } from './utils'; import createStyle from './styles/circle.style'; import { useTheme } from '../Theme/Theme'; type strokeLinecap = 'butt' | 'square' | 'round'; const defaultWidth = 120; const defaultHeight = 120; const defaultStrokeWidth = 6; const CircleProgress = (props: ProgressProps) => { const { progressStyle, percent = 0, format, status = 'normal', strokeColor = undefined, trailColor = undefined, size = 'default', pivotText, showInfo = true, children, animated = false, animationDuration = 200, onAnimationEnd, } = props; const token = useTheme(); const styles = createStyle(token); const validPercent = useMemo(() => validProgress(percent), [percent]); const { circleWidth, circleHeight, strokeWidth, circleColor, circleInfo } = useMemo(() => { let progressWidth = defaultWidth; let progressHeight = defaultHeight; let borderWidth = defaultStrokeWidth; if (size === 'small') { progressWidth = defaultWidth / 2; progressHeight = defaultHeight / 2; borderWidth = defaultStrokeWidth / 2; } if (isNumber(size)) { progressWidth = size; progressHeight = size; } if (isArray(size)) { progressWidth = (size?.[0] ?? defaultWidth) as number; progressHeight = (size?.[0] ?? defaultHeight) as number; borderWidth = (size?.[1] ?? defaultStrokeWidth) as number; } const firstText = renderTextLikeJSX(pivotText, styles.pivot, { numberOfLines: 1, }); let secondText = renderTextLikeJSX(`${validPercent}%`, styles.pivot, { numberOfLines: 1, }); let color: string = token['--color-primary-6']; if (status === 'success' && validPercent >= 100) { color = token['--color-success-7']; secondText = ( ); } if (status === 'exception') { color = token['--color-danger-7']; secondText = ( ); } const realPivotText = firstText || secondText; const formatText = format ? format(validPercent, realPivotText) : realPivotText; return { // 圆环宽 circleWidth: progressWidth, // 圆环高 circleHeight: progressHeight, // 圆形进度条宽度 strokeWidth: borderWidth, // 圆形进度条颜色 circleColor: strokeColor || color, // 圆形进度条中间的文案 circleInfo: ( {children || formatText} ), }; }, [validPercent, size, status, showInfo, format]); const radius = circleWidth / 2; // 外层半径 const innerRadius = radius - strokeWidth; // 内层半径 const circumference = 2 * innerRadius * Math.PI; // 总周长(与实际绘制半径一致) const progress = useRef(new Animated.Value(validPercent / 100)); const radian = progress.current.interpolate({ inputRange: [0, 1], outputRange: [2 * Math.PI, 0], }); const circumferenceWithProgress = Animated.multiply(innerRadius, radian); const AnimatedCircleProgress = Animated.createAnimatedComponent(Circle); // 圆环背景色,支持单色和多颜色组成的渐变色 const linearGradientColors = isArray(circleColor) ? circleColor.map((i, j) => ({ stop: `${(((j + 1) / circleColor.length) * 100).toFixed(2) + '%'}`, color: i as string, })) : []; const outerCircleCommonConfig = { fill: 'none', cx: radius, cy: radius, r: innerRadius, strokeLinecap: 'round' as strokeLinecap, strokeWidth: strokeWidth, strokeDasharray: `${circumference} ${circumference}`, }; const onAnimationEndPersistFn = usePersistFn((n: number) => { onAnimationEnd?.(n); }); useEffect(() => { if (!animated) { // 非动画模式下,直接设置当前进度 progress.current.setValue(validPercent / 100); return; } const action = Animated.timing(progress.current, { toValue: validPercent / 100, duration: animated ? animationDuration : 0, easing: Easing.linear, useNativeDriver: false, }); action.start(({ finished }) => { if (finished) { onAnimationEndPersistFn(validPercent); } }); return () => { action.stop(); }; }, [ progress, validPercent, animationDuration, animated, onAnimationEndPersistFn, ]); return ( {linearGradientColors.map((item, index) => ( ))} {circleInfo} ); }; export default CircleProgress;