Last active
November 26, 2022 07:14
-
-
Save Aryk/434bcce78705eff0a204ed8b450f80fd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {useEffect, useRef} from "react"; | |
import { | |
ColorValue, | |
StyleProp, | |
ViewStyle, View, StyleSheet, TextStyle, Platform, | |
} from "react-native"; | |
import Animated, { | |
useSharedValue, | |
withTiming, | |
useAnimatedProps, | |
useDerivedValue, | |
useAnimatedStyle, | |
withDelay, | |
Easing, | |
interpolateColor, | |
createAnimatedPropAdapter, | |
processColor, | |
} from 'react-native-reanimated'; | |
import { ReText } from 'react-native-redash'; | |
import Svg, { Circle } from 'react-native-svg'; | |
const AnimatedCircle = Animated.createAnimatedComponent(Circle); | |
interface IAnimatedProgressWheelWithCount { | |
size?: number; | |
duration?: number; | |
animateOnMount?: number | boolean; | |
easing?: Animated.EasingFunction; | |
progress?: number; // 0 to 100 | |
progressPrepend?: string; | |
progressAppend?: string; | |
backgroundColor?: ColorValue; | |
backgroundStrokeColor?: string; | |
backgroundStrokeWidth?: number; | |
strokeColor?: string; | |
strokeColorStart?: string; | |
strokeWidth?: number; | |
scaleInitial?: number; | |
scaleDuration?: number; | |
scaleEasing?: Animated.EasingFunction; | |
textStyle?: StyleProp<TextStyle> | |
containerStyle?: StyleProp<ViewStyle>; | |
} | |
const AnimatedProgressWheelWithCount = ({ | |
size = 80, | |
duration = 3000, | |
animateOnMount, | |
easing = Easing.out(Easing.ease), | |
progress = 0.5, | |
progressPrepend = "", | |
progressAppend = "%", | |
backgroundColor, | |
backgroundStrokeColor = 'orange', | |
backgroundStrokeWidth = size / 10, | |
strokeColor = 'blue', | |
strokeColorStart = strokeColor, | |
strokeWidth = backgroundStrokeWidth, | |
scaleInitial = 0.4, | |
scaleDuration = duration * 0.7, | |
scaleEasing = Easing.elastic(3), | |
textStyle, | |
containerStyle, | |
}: IAnimatedProgressWheelWithCount) => { | |
progress /= 100; | |
const width = size; | |
const height = size; | |
const radius = ((size - Math.max(backgroundStrokeWidth, strokeWidth)) / 2); | |
const circumference = radius * 2 * Math.PI; | |
const progressValue = useSharedValue(0); | |
const progressText = useDerivedValue(() => `${progressPrepend}${Math.floor(progressValue.value * 100)}${progressAppend}`, [progressPrepend, progressAppend]); | |
const scaleValue = useSharedValue(scaleInitial); | |
const transform = useAnimatedStyle(() => ({transform: [{scale: scaleValue.value}]})); | |
// const reset = () => { | |
// progressValue.value = 0; | |
// scaleValue.value = scaleInitial; | |
// } | |
// On Android, app was crashing and this seems to fix the issue on Android | |
// https://github.com/software-mansion/react-native-reanimated/issues/3775#issuecomment-1317205571 | |
const adapter = Platform.OS === "android" ? | |
createAnimatedPropAdapter( | |
(props) => { | |
if (Object.keys(props).includes('stroke')) { | |
props.stroke = { | |
type: 0, | |
payload: processColor(props.stroke as string), | |
}; | |
} | |
}, | |
['stroke'] | |
) : | |
null; | |
const animatedProps = useAnimatedProps( | |
() => ({ | |
stroke: interpolateColor( | |
progressValue.value, | |
[0, progress], | |
[strokeColorStart, strokeColor], | |
), | |
strokeDashoffset: circumference * (1 - progressValue.value), | |
}), | |
[strokeColorStart, strokeColor, circumference], | |
adapter, | |
); | |
const move = ({delay = 0} = {}) => { | |
scaleValue.value = withDelay(delay, withTiming(1, { duration: scaleDuration, easing: scaleEasing })); | |
progressValue.value = withDelay(delay, withTiming(progress, { duration, easing })); | |
}; | |
const mountedRef = useRef(false); | |
useEffect( | |
() => { | |
if (!mountedRef.current && animateOnMount) { | |
const delay = typeof(animateOnMount) === "number" ? animateOnMount : 0; | |
move({delay}); | |
// Progress changed so move it to the new progress value. | |
} else if (mountedRef.current) { | |
move(); | |
} | |
mountedRef.current = true; | |
}, | |
[progress], | |
); | |
return <> | |
<Animated.View style={[ | |
{ | |
backgroundColor, | |
alignItems: 'center', | |
justifyContent: 'center', | |
width: size, | |
height: size, | |
}, | |
transform, | |
containerStyle, | |
]}> | |
<Svg style={{ transform: [{ rotate: '-90deg' }] }}> | |
<Circle | |
cx={width / 2} | |
cy={height / 2} | |
r={radius} | |
stroke={backgroundStrokeColor} | |
strokeWidth={backgroundStrokeWidth} | |
/> | |
<AnimatedCircle | |
cx={width / 2} | |
cy={height / 2} | |
r={radius} | |
// stroke={strokeColor} | |
strokeWidth={strokeWidth} | |
strokeDasharray={circumference} | |
animatedProps={animatedProps} | |
strokeLinecap="round" | |
/> | |
</Svg> | |
<View style={[StyleSheet.absoluteFill, {alignItems: "center", justifyContent: "center"}]}> | |
<ReText | |
style={[ | |
{ | |
fontSize: size / 3.8, | |
color: 'purple', | |
textAlign: 'center', | |
marginLeft: size / 40, // slight nudge to the right to make it look more centered. | |
}, | |
textStyle, | |
]} | |
text={progressText} | |
/> | |
</View> | |
</Animated.View> | |
</> | |
} | |
export { | |
IAnimatedProgressWheelWithCount, | |
AnimatedProgressWheelWithCount, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment