sefirosu
sefirosu

Reputation: 2648

React native animation progress bar

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

enter image description here

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

Edited:

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

Answers (2)

Getsumi3
Getsumi3

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

docmurloc
docmurloc

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

Related Questions