Reputation: 2648
I am trying to build a progress bar for my react-native project It should be a generic component to be used in many places. Please see my code:
The progress bar tsx:
import React, { useEffect } from 'react'
import { Animated, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
interface Props {
total: number
progress: number
color?: string
backgroundColor?: string
height?: number
style?: StyleProp<ViewStyle>
animDelay?: number
animDuration?: number
testID?: string
}
const ProgressBar = ({
color,
backgroundColor,
style,
height,
animDelay,
animDuration,
total,
progress,
testID = 'progress-bar',
}: Props): JSX.Element => {
const minWidthValue = 5.4
const percentage = total && progress ? Math.min(progress, total) / total : 0
const minDisplayWidth =
percentage > 0 && percentage < minWidthValue ? minWidthValue : percentage
const barWidth = `${Math.max(minDisplayWidth, Math.floor(percentage * 100))}%`
useEffect(() => {
const animationValue = new Animated.Value(0)
Animated.timing(animationValue, {
toValue: progress,
delay: animDelay,
duration: animDuration,
useNativeDriver: true,
}).start();
}, []);
return (
<View
testID={testID}
style={[
styles.container,
height ? { height } : undefined,
backgroundColor ? { backgroundColor } : undefined,
style,
]}>
<Animated.View
style={[
styles.bar,
{
backgroundColor: color,
width: barWidth,
},
]}
/>
</View>
)
}
export default ProgressBar
const BORDER_RADIUS = 15
const styles = StyleSheet.create({
bar: {
borderRadius: BORDER_RADIUS,
height: '100%',
},
container: {
borderRadius: BORDER_RADIUS,
flexDirection: 'row',
height: 30,
overflow: 'hidden',
width: '100%',
},
})
And the example of usesage, say on home.tsx:
<ProgressBarWrapper total={100} progress={50} testID='test-id-test-1' />
So what happen is, the total length(100%) , and I wish the animation moving from 0 to 50 in the bar at beginning when this component loaded up
With above code, I only get a static bar , not moving at all.. Could someone point me out where I have done wrong? Sample code would be very helpful Cheers
I have updated code
useNativeDriver
and use interpolation
here to avoid oscillations using 'clamp'
However, the animation still not moving/working , wondering any advise on this. Thanks
const ProgressBarInternal = ({
color,
backgroundColor,
style,
height,
animDelay,
animDuration,
total,
progress,
testID = 'progress-bar',
borderRadius,
containerHeight,
onAnimationDidEnd,
}: Props): JSX.Element => {
const minWidthValue = 5.4
const percentage = total && progress ? Math.min(progress, total) / total : 0
const minDisplayWidth =
percentage > 0 && percentage < minWidthValue ? minWidthValue : percentage
const progressingBarWidthPercentage = `${Math.max(minDisplayWidth, Math.floor(percentage * 100))}%`
const animatingProgressBar = useRef(new Animated.Value(0))
useEffect(() => {
Animated.timing(animatingProgressBar.current, {
toValue: 0,
delay: animDelay,
duration: animDuration,
useNativeDriver: true,
}).start()
}, []
);
return (
<View
testID={testID}
style={...}>
<Animated.View
style={[
styles.bar,
{ borderRadius: borderRadius },
{
backgroundColor: color,
width: progressingBarWidthPercentage,
},
{
transform: [
{
translateX: animatingProgressBar.current.interpolate({
inputRange: [1, 100],
outputRange: ["0%", "100%"],
extrapolate: "clamp"
}),
},
],
},
]}
/>
</View>
)
}
Upvotes: 1
Views: 10359
Reputation: 361
I encountered similar behavior when useNativeDriver was set to true.
Changing useNativeDriver to false may fix your issue
Also here is an example of how I achieved animated width:
const barWidth = useRef(new Animated.Value(0)).current;
const progressPercent = barWidth.interpolate({
inputRange: [0, 100],
outputRange: ["0%", `100%`],
});
useEffect(() => {
animatedController.setValue(0)
Animated.timing(barWidth, {
duration: 5000,
toValue: 100,
useNativeDriver: false
}).start();
}, [])
return (
<View style={...}>
<Animated.View
style={[
styles.bar,
{
backgroundColor: color,
width: progressPercent,
}
]}
/>
</View>
)
Upvotes: 3
Reputation: 1239
You can not animate with the width property.
Use transform instead.
An example with the base of your code.
import React, { useEffect, useRef } from 'react'
import { Animated, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
interface Props {
total: number
progress: number
color?: string
backgroundColor?: string
height?: number
style?: StyleProp<ViewStyle>
animDelay?: number
animDuration?: number
testID?: string
}
const Test = ({
color,
backgroundColor,
style,
height,
animDelay,
animDuration,
total,
progress,
testID = 'progress-bar',
}: Props): JSX.Element => {
//const minWidthValue = 5.4
//const percentage = total && progress ? Math.min(progress, total) / total : 0
//const minDisplayWidth =
// percentage > 0 && percentage < minWidthValue ? minWidthValue : percentage
//const barWidth = `${Math.max(minDisplayWidth, Math.floor(percentage * 100))}%`
const barWidth = useRef(new Animated.Value(1)).current;
useEffect(() => {
//const animationValue = new Animated.Value(0)
Animated.timing(barWidth, {
toValue: 9,//progress,
//delay: 1000,//animDelay,
duration: 9000,//animDuration,
useNativeDriver: true,
}).start();
}, []
);
return (
<View
testID={testID}
style={[
styles.container,
height ? { height } : undefined,
backgroundColor ? { backgroundColor } : undefined,
style,
]}>
<Animated.View
style={[
styles.bar,
{
backgroundColor: color,
width: 50,
},
{transform: [{ scaleX: barWidth }]}
]}
/>
</View>
)
}
export default Test
const BORDER_RADIUS = 5
const styles = StyleSheet.create({
bar: {
borderRadius: BORDER_RADIUS,
height: '100%',
},
container: {
borderRadius: BORDER_RADIUS,
flexDirection: 'row',
height: 30,
overflow: 'hidden',
width: '100%',
},
})
Upvotes: 1