react-native-animated-circular-progress
Version:
Animated circular progress components for React Native project, with all pure React Native API
229 lines (204 loc) • 6.85 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import { View, StyleSheet, Animated, ViewPropTypes, Easing } from 'react-native';
// compatibility for react-native versions < 0.44
const ViewPropTypesStyle = ViewPropTypes ? ViewPropTypes.style : View.propTypes.style;
export default class AnimatedCircularProgress extends React.PureComponent {
static propTypes = {
backgroundColor: PropTypes.string,
color: PropTypes.string,
startDeg: PropTypes.number,
endDeg: PropTypes.number,
radius: PropTypes.number,
innerRadius: PropTypes.number,
innerBackgroundColor: PropTypes.string,
duration: PropTypes.number,
children: PropTypes.node,
style: ViewPropTypesStyle,
};
static defaultProps = {
backgroundColor: '#eeeeee',
color: '#1e88e5',
startDeg: 0,
endDeg: 360,
radius: 100,
innerRadius: 80,
innerBackgroundColor: 'transparent',
duration: 1000,
children: null,
style: null,
};
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
const { duration, innerRadius, radius, startDeg, endDeg } = this.props;
if (startDeg > endDeg) console.warn('AnimatedCircularProgress: startDeg must smaller than endDeg');
if (innerRadius > radius)
console.warn('AnimatedCircularProgress: innerRadius must smaller than radius');
Animated.timing(this.animatedValue, {
toValue: 1,
duration: (duration / 360) * (endDeg - startDeg),
useNativeDriver: false,
easing: Easing.linear,
}).start();
}
renderHalf = (color, transforms = [], otherStyle = {}) => {
const { radius } = this.props;
return (
<Animated.View
style={[
styles.half,
{ backgroundColor: color, width: radius, height: radius * 2 },
{
transform: [{ translateX: radius / 2 }, ...transforms, { translateX: -radius / 2 }],
},
otherStyle,
]}
/>
);
};
renderCircle = ({
backgroundColor,
//
color1,
zIndex1 = 0,
rotate1,
//
color2,
zIndex2,
rotate2,
//
color3,
zIndex3 = 0,
rotate3,
}) => {
const { radius, innerRadius, style, children, innerBackgroundColor } = this.props;
return (
<View
style={[
styles.outerStyle,
{
width: radius * 2,
height: radius * 2,
borderRadius: radius,
backgroundColor,
},
style,
]}
>
{this.renderHalf(color1, [{ rotate: rotate1 }], { zIndex: zIndex1 })}
{this.renderHalf(color2, [{ rotate: rotate2 }], { zIndex: zIndex2 })}
{this.renderHalf(color3, [{ rotate: rotate3 }], { zIndex: zIndex3 })}
<View
style={[
styles.innerStyle,
{
width: innerRadius * 2,
height: innerRadius * 2,
borderRadius: innerRadius,
left: radius - innerRadius,
top: radius - innerRadius,
backgroundColor: innerBackgroundColor,
},
]}
>
{children}
</View>
</View>
);
};
render() {
const { backgroundColor, color, startDeg, endDeg } = this.props;
let circleBg;
let color1;
let color2;
let color3;
let zIndex2;
let rotate1;
let rotate2;
let rotate3;
if (startDeg <= 180 && endDeg <= 180) {
rotate1 = '0deg';
rotate2 = '180deg';
rotate3 = this.animatedValue.interpolate({
inputRange: LIST_TIMING_ANIMATION_INPUT_2_VALUE,
outputRange: [`${180 + startDeg}deg`, `${180 + endDeg}deg`],
});
color1 = backgroundColor;
color2 = color;
color3 = backgroundColor;
zIndex2 = 0;
} else if (startDeg > 180 && endDeg > 180) {
circleBg = color;
rotate1 = this.animatedValue.interpolate({
inputRange: LIST_TIMING_ANIMATION_INPUT_2_VALUE,
outputRange: [`${180 + startDeg}deg`, `${180 + endDeg}deg`],
});
rotate2 = '180deg';
rotate3 = '180deg';
color1 = backgroundColor;
color2 = color;
color3 = color;
zIndex2 = 0;
} else {
const total = endDeg - startDeg;
const part1 = 180 - startDeg;
// 0..1
// startDeg...180...180...180...endDeg
const listTimingAnimationInput = [0, part1 / total, part1 / total, part1 / total, 1];
rotate1 = '0deg';
rotate2 = '180deg';
rotate3 = this.animatedValue.interpolate({
inputRange: listTimingAnimationInput,
outputRange: [
`${180 + startDeg}deg`,
`${180 + 180}deg`,
`${180 + 180}deg`,
`${180 + 180}deg`,
`${180 + endDeg}deg`,
],
});
color1 = this.animatedValue.interpolate({
inputRange: listTimingAnimationInput,
outputRange: [backgroundColor, backgroundColor, color, color, color],
});
color2 = color;
color3 = backgroundColor;
zIndex2 = this.animatedValue.interpolate({
inputRange: listTimingAnimationInput,
outputRange: [0, 0, 0, 1, 1],
});
}
return this.renderCircle({
backgroundColor: circleBg,
//
color1,
rotate1,
//
color2,
zIndex2,
rotate2,
//
color3,
rotate3,
});
}
}
const LIST_TIMING_ANIMATION_INPUT_2_VALUE = [0, 1];
const styles = StyleSheet.create({
outerStyle: {
overflow: 'hidden',
},
half: {
position: 'absolute',
left: 0,
top: 0,
},
innerStyle: {
position: 'absolute',
backgroundColor: '#fff',
zIndex: 2,
},
});