Skip to content

Commit 1e05b97

Browse files
author
hy
committed
fix(Progress) 添加Progress进度圈组件,优化Progress组件
1 parent d42e3a4 commit 1e05b97

File tree

2 files changed

+144
-172
lines changed

2 files changed

+144
-172
lines changed

example/examples/src/routes/Progress/index.tsx

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import React, {useState, useEffect} from 'react';
2-
import {View, Text} from 'react-native';
3-
import {Progress, Button, Spacing} from '@uiw/react-native';
4-
import Layout, {Container} from '../../Layout';
5-
import {motorcycle} from './svg';
1+
import React, { useState, useEffect } from 'react';
2+
import { View, Text } from 'react-native';
3+
import { Progress, Button, Spacing } from '@uiw/react-native';
4+
import Layout, { Container } from '../../Layout';
5+
import { motorcycle } from './svg';
66

7-
const {Header, Body, Card} = Layout;
7+
const { Header, Body, Card } = Layout;
88

99
const ProgressDemo = (props: any) => {
10-
const {route} = props;
10+
const { route } = props;
1111
const description = route.params.description;
1212
const title = route.params.title;
1313

@@ -25,44 +25,15 @@ const ProgressDemo = (props: any) => {
2525
return (
2626
<Container>
2727
<Header title={title} description={description} />
28-
<Body style={{paddingLeft: 16, paddingRight: 16}}>
28+
<Body style={{ paddingLeft: 16, paddingRight: 16 }}>
2929
<Header description={'基本使用'} />
30-
<Progress
31-
// progressColor="#5847FF"
32-
progress={40}
33-
progressShow={false}
34-
/>
30+
<Progress type='circle' />
31+
<Header description={'基本使用'} />
32+
<Progress type='circle' />
3533
<Header description={'展示进度图标'} />
36-
<Progress
37-
// progressColor="#5847FF"
38-
progressShow={false}
39-
iconShow={true}
40-
progress={30}
41-
/>
34+
<Progress type='line' />
4235
<Header description={'改变进度图标'} />
43-
<Progress
44-
// progressColor="#5847FF"
45-
iconShow={true}
46-
progressShow={false}
47-
progress={60}
48-
xml={motorcycle}
49-
/>
50-
<Header description={'点击变化'} />
51-
<View
52-
style={{
53-
flexDirection: 'column',
54-
marginBottom: 10,
55-
}}>
56-
<Progress
57-
progress={val}
58-
// progressColor="#9185FF"
59-
iconShow={true}
60-
xml={motorcycle}
61-
/>
62-
<Button color={'#3578e5'} onPress={onPress}>
63-
(+-)10
64-
</Button>
65-
</View>
36+
<Progress type='circle' />
6637
</Body>
6738
</Container>
6839
);
Lines changed: 131 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,143 @@
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';
74

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;
2817
}
2918

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;
4633

4734
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);
5043
}
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]);
6045

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);
6490

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+
);
68105

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>
102133
</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+
}
116138
};
117139

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

Comments
 (0)