Skip to content

Instantly share code, notes, and snippets.

@Aryk
Last active November 26, 2022 07:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Aryk/434bcce78705eff0a204ed8b450f80fd to your computer and use it in GitHub Desktop.
Save Aryk/434bcce78705eff0a204ed8b450f80fd to your computer and use it in GitHub Desktop.
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