|
1 | | -import React, { useRef, useState, useEffect } from 'react'; |
2 | | -import { Animated, View, StyleSheet, ViewProps, LayoutChangeEvent, Text } from 'react-native'; |
3 | | -import { run } from './svg'; |
4 | | -import Icon from '../Icon'; |
5 | | -import { Theme } from '../theme'; |
6 | | -import { useTheme } from '@shopify/restyle'; |
| 1 | +import React, { useEffect, useRef } from 'react'; |
| 2 | +import { View, Text, Animated } from 'react-native'; |
| 3 | +import Svg, { Circle, G } from 'react-native-svg'; |
7 | 4 |
|
8 | | -type PositionType = 'fixed' | 'relative'; |
9 | | - |
10 | | -// 组件的属性定义 |
11 | | -export interface ProgressProps extends ViewProps { |
12 | | - /** 当前进度百分比, 0 - 100, 默认0 */ |
13 | | - progress?: number; |
14 | | - /** 颜色 */ |
15 | | - progressColor?: string; |
16 | | - /** 位置 */ |
17 | | - position?: PositionType; |
18 | | - /** 动画持续的时间 */ |
19 | | - animation?: { duration?: number } | boolean; |
20 | | - /** 图标源 */ |
21 | | - xml?: string; |
22 | | - /** 是否展示图标 */ |
23 | | - iconShow?: boolean; |
24 | | - /** 图标尺寸 */ |
25 | | - size?: number; |
26 | | - /** 是否展示进度提示字 */ |
27 | | - progressShow?: boolean; |
| 5 | +interface ProgressProps { |
| 6 | + type: 'line' | 'circle'; |
| 7 | + color?: string | [string, string]; |
| 8 | + bgColor?: string; |
| 9 | + strokeWidth?: number; |
| 10 | + value?: number; |
| 11 | + showLabel?: boolean; |
| 12 | + labelPosition?: 'right' | 'top'; |
| 13 | + label?: React.ReactNode; |
| 14 | + showUnit?: boolean; |
| 15 | + innerWidth?: number; |
| 16 | + width?: number; |
28 | 17 | } |
29 | 18 |
|
30 | | -export default (props: ProgressProps) => { |
31 | | - const theme = useTheme<Theme>(); |
32 | | - const { |
33 | | - iconShow = false, |
34 | | - progressShow = true, |
35 | | - size = 25, |
36 | | - xml = run, |
37 | | - style, |
38 | | - progress = 0, |
39 | | - progressColor = theme.colors.primary_background || '#3578e5', |
40 | | - position, |
41 | | - animation = { duration: 500 }, |
42 | | - } = props; |
43 | | - |
44 | | - const progWidth = useRef<any>(new Animated.Value(0)).current; |
45 | | - const [wrapWidth, setWrapWidth] = useState<number>(0); |
| 19 | +const Progress: React.FC<ProgressProps> = ({ |
| 20 | + type, |
| 21 | + color = '#3578e5', |
| 22 | + bgColor = '#e5e5e5', |
| 23 | + strokeWidth = 20, |
| 24 | + value = 50, |
| 25 | + showLabel = true, |
| 26 | + labelPosition = 'right', |
| 27 | + label, |
| 28 | + showUnit = true, |
| 29 | + innerWidth = 15, |
| 30 | + width = 100, |
| 31 | +}) => { |
| 32 | + const progressValue = useRef(new Animated.Value(0)).current; |
46 | 33 |
|
47 | 34 | useEffect(() => { |
48 | | - if (wrapWidth && progress) { |
49 | | - startAnimation(); |
| 35 | + try { |
| 36 | + Animated.timing(progressValue, { |
| 37 | + toValue: value, |
| 38 | + duration: 1000, |
| 39 | + useNativeDriver: true, |
| 40 | + }).start(); |
| 41 | + } catch (error) { |
| 42 | + console.log(error); |
50 | 43 | } |
51 | | - }, [wrapWidth, progress]); |
52 | | - |
53 | | - const startAnimation = () => { |
54 | | - Animated.timing(progWidth, { |
55 | | - toValue: getWidth(), |
56 | | - duration: typeof animation !== 'boolean' ? animation.duration : 1000, |
57 | | - useNativeDriver: false, |
58 | | - }).start(); |
59 | | - }; |
| 44 | + }, [value]); |
60 | 45 |
|
61 | | - const onLayout = (e: LayoutChangeEvent) => { |
62 | | - setWrapWidth(e.nativeEvent.layout.width); |
63 | | - }; |
| 46 | + if (type === 'line') { |
| 47 | + const progressLabel = showLabel && ( |
| 48 | + <Text |
| 49 | + style={{ |
| 50 | + position: 'absolute', [labelPosition]: 0, |
| 51 | + paddingHorizontal: 5, |
| 52 | + top: '50%', |
| 53 | + transform: [{ translateY: -7.5 }] |
| 54 | + }}> |
| 55 | + {label ?? `${value}${showUnit ? '%' : ''}`} |
| 56 | + </Text> |
| 57 | + ); |
| 58 | + return ( |
| 59 | + <View style={{ height: strokeWidth, width: width, backgroundColor: bgColor, borderRadius: 15 }}> |
| 60 | + <Animated.View |
| 61 | + style={{ |
| 62 | + backgroundColor: color, |
| 63 | + height: strokeWidth, |
| 64 | + borderRadius: 15, |
| 65 | + width: `${value}%`, |
| 66 | + transform: [{ |
| 67 | + translateX: progressValue.interpolate({ |
| 68 | + inputRange: [0, 100], |
| 69 | + outputRange: [-strokeWidth / 2, strokeWidth / 2] |
| 70 | + }) |
| 71 | + }] |
| 72 | + }}> |
| 73 | + <View style={{ height: strokeWidth, borderRadius: 15 }} /> |
| 74 | + </Animated.View> |
| 75 | + {progressLabel} |
| 76 | + </View> |
| 77 | + ); |
| 78 | + } else if (type === 'circle') { |
| 79 | + const radius = (width - strokeWidth) / 2; |
| 80 | + const circumference = radius * 2 * Math.PI; |
| 81 | + const progress = progressValue.interpolate({ |
| 82 | + inputRange: [0, 100], |
| 83 | + outputRange: [0, 1], |
| 84 | + }); |
| 85 | + const progressDashoffset = progress.interpolate({ |
| 86 | + inputRange: [0, 1], |
| 87 | + outputRange: [circumference, 0], |
| 88 | + }); |
| 89 | + const AnimatedCircle = Animated.createAnimatedComponent(Circle); |
64 | 90 |
|
65 | | - const getWidth = (percent: number = progress) => { |
66 | | - return wrapWidth * (normalPercent(percent) / 100); |
67 | | - }; |
| 91 | + const progressLabel = showLabel && ( |
| 92 | + <Text |
| 93 | + style={{ |
| 94 | + position: 'absolute', |
| 95 | + top: '50%', |
| 96 | + left: width / 2, |
| 97 | + transform: [{ translateX: -15 }, { translateY: -10 }], |
| 98 | + fontSize: 18, |
| 99 | + fontWeight: 'bold', |
| 100 | + color: typeof color === 'string' ? color : color[1], |
| 101 | + }}> |
| 102 | + {label ?? `${value}${showUnit ? '%' : ''}`} |
| 103 | + </Text> |
| 104 | + ); |
68 | 105 |
|
69 | | - const normalPercent = (percent?: number) => { |
70 | | - let widthPercent: any = 0; |
71 | | - if (percent !== undefined && percent > 0) { |
72 | | - widthPercent = percent > 100 ? 100 : percent; |
73 | | - } |
74 | | - return widthPercent; |
75 | | - }; |
76 | | - |
77 | | - return ( |
78 | | - <View style={[styles.container, style]}> |
79 | | - <View |
80 | | - onLayout={onLayout} |
81 | | - style={[ |
82 | | - styles.pre, |
83 | | - position === 'fixed' ? { position: 'absolute', top: 0 } : {}, |
84 | | - { borderColor: progressColor, height: progressShow === true ? 20 : 4 }, |
85 | | - ]} |
86 | | - > |
87 | | - {progressShow && progressShow === true && ( |
88 | | - <View style={{ position: 'absolute', left: '45%', zIndex: 1000 }}> |
89 | | - <Text style={{ fontSize: 12 }}>{progress}%</Text> |
90 | | - </View> |
91 | | - )} |
92 | | - <Animated.View |
93 | | - style={[ |
94 | | - styles.preOisn, |
95 | | - { |
96 | | - width: progWidth, |
97 | | - height: progressShow === true ? 20 : 4, |
98 | | - backgroundColor: progressColor, |
99 | | - }, |
100 | | - ]} |
101 | | - ></Animated.View> |
| 106 | + return ( |
| 107 | + <View> |
| 108 | + {progressLabel} |
| 109 | + <Svg width={width} height={width}> |
| 110 | + <G rotation="-90" origin={`${width / 2}, ${width / 2}`}> |
| 111 | + <Circle |
| 112 | + cx={width / 2} |
| 113 | + cy={width / 2} |
| 114 | + r={radius} |
| 115 | + stroke={bgColor} |
| 116 | + strokeWidth={innerWidth > strokeWidth ? strokeWidth : innerWidth} |
| 117 | + strokeOpacity={1} |
| 118 | + fill="none" |
| 119 | + /> |
| 120 | + <AnimatedCircle |
| 121 | + cx={width / 2} |
| 122 | + cy={width / 2} |
| 123 | + r={radius} |
| 124 | + stroke={typeof color === 'string' ? color : color[1]} |
| 125 | + strokeWidth={innerWidth} |
| 126 | + strokeDasharray={`${circumference}, ${circumference}`} |
| 127 | + strokeDashoffset={progressDashoffset} |
| 128 | + strokeLinecap="round" |
| 129 | + fill="none" |
| 130 | + /> |
| 131 | + </G> |
| 132 | + </Svg> |
102 | 133 | </View> |
103 | | - {iconShow && iconShow === true && ( |
104 | | - <View onLayout={onLayout} style={[styles.preIcon, { height: size }]}> |
105 | | - <Animated.View |
106 | | - style={{ |
107 | | - marginLeft: progress === 0 ? -50 : progress === 100 ? -20 : -35, |
108 | | - width: progWidth, |
109 | | - }} |
110 | | - ></Animated.View> |
111 | | - <Icon xml={xml} size={size} /> |
112 | | - </View> |
113 | | - )} |
114 | | - </View> |
115 | | - ); |
| 134 | + ); |
| 135 | + } else { |
| 136 | + return null; |
| 137 | + } |
116 | 138 | }; |
117 | 139 |
|
118 | | -const styles = StyleSheet.create({ |
119 | | - container: { |
120 | | - position: 'relative', |
121 | | - flex: 1, |
122 | | - }, |
123 | | - pre: { |
124 | | - borderWidth: 1, |
125 | | - width: '100%', |
126 | | - borderRadius: 20, |
127 | | - marginBottom: 0, |
128 | | - marginTop: 0, |
129 | | - overflow: 'hidden', |
130 | | - }, |
131 | | - preIcon: { |
132 | | - width: '100%', |
133 | | - overflow: 'hidden', |
134 | | - flexDirection: 'row', |
135 | | - paddingHorizontal: 20, |
136 | | - }, |
137 | | - preOisn: { |
138 | | - position: 'absolute', |
139 | | - left: 0, |
140 | | - top: 0, |
141 | | - }, |
142 | | -}); |
| 140 | +export default Progress; |
| 141 | + |
| 142 | + |
| 143 | + |
0 commit comments