online software programming courses

React native animate width

React native animate width progress bar could be quite problemetic for beginners. In this complete tutorial I will cover how to animate width and do interpolation if you want increase the width.

 

Build a mobile app with Laravel Backend 

We started it by delcaring an array of questions 

const questions=[
        "Do you love React Native?",
        "Do you want to be good at this?",
        "Do you follow me to learn React Native?",
        "Are you a beginner?",
        "Follow me",
];

We have used four state variables

const [index, setIndex] =useState(0);
const [current, setCurrent] = useState(new Animated.Value(0));
const [progress, setProgress] = useState(new Animated.Value(0));
const [running, setRunning] = useState(1);

The first one is to keep the track of the index of our array of questions. With this index, we know which questions are dealing with.

The second one is to interpolate the current and next questions. We do interpolation and transform the x position.

The third one is the interpolate width of the progress bar. This is for increasing the width percentage of the bar over time.

The last one is to change the view to a new view.

If you closely notice, you will find that the second and third variables are initialized with new Animated.Value() function. We do it, because we want to interpolate those variables 

const currentAnim=current.interpolate({
        inputRange:[0, 1],
        outputRange:[0, -width]
});

const nextAnim=current.interpolate({
        inputRange:[0, 1],
        outputRange:[width, 0]
});

Our currentAnim moves the current question position to 0 at the beginning of the animation. As we interpolate through, the new position is the out of the screen which is -width.

Variable nextAnim moves the next question position. It's invisible as we outputRange is equal to width which is also out of the screen position. 

let nextQuestion = "";
const quesiton = questions[index];
if(index<questions.length){
        nextQuestion=questions[index+1];
}

The variable nextQuestion, shows the next question on the view. When a new render happens, it keeps the track of the nextQuestion using the if statement. 

A new render happens every time we change the state variable. See the code below

const handlePress=()=>{
            Animated.parallel([
                Animated.timing(current, {
                    toValue:1,
                    duration:400,
                    useNativeDriver:false,
                }),
            Animated.timing(progress, {
                toValue:index+1,
                duration:400,
                useNativeDriver:false
            })
            ]).start(()=>{
                setIndex(index+1);
                current.setValue(0);

                if(index+1>=5){
                    setTimeout(()=>{
                        setRunning(0)
                    }, 500)
                }

            })
}

From the above code you will find that, we are changing the state variables using setIndex(index+1) and current.setValue(0). These two lines causes rerender of react component.

And it happens every time when animation finishes.

Now let's see when the width interpolation happens. The below code

Animated.timing(progress, {
                toValue:index+1,
                duration:400,
                useNativeDriver:false
})

You see that, toValue is equal to index+1. So on the first press on the button, the value is 1, next it's 2 and finally it's 4.

As animation goes on toValue is changed. The final value is quesitons.length. See the code

const progressAnim=progress.interpolate({
        inputRange:[0, questions.length],
        outputRange:["20%", "100%"],
});

inputRange and toValue they have one on one mapping relation. That's we can change the toValue to reach our final value based on inputRange.

As we are interpolating width, the outputRange is in string format. At the same time, we use percentage value, since ti's correlated with number of objects we have in the array.

We used useNativeDriver to false, since native drivers don't support width interpolation. 

 

import React,{ useState} from 'react';
import { StyleSheet, TouchableOpacity, Text, View, Animated, Dimensions, ActivityIndicator, } from "react-native";
import LottieView from 'lottie-react-native';
export default function Progressbar(){
    const [index, setIndex] =useState(0);
    const [current, setCurrent] = useState(new Animated.Value(0));
    const [progress, setProgress] = useState(new Animated.Value(0));
    const [running, setRunning] = useState(1);
    const questions=[
        "Do you love React Native?",
        "Do you want to be good at this?",
        "Do you follow me to learn React Native?",
        "Are you a beginner?",
        "Follow me",
    ];
    let nextQuesiton = "";
    const quesiton = questions[index];
    if(index<questions.length){
        nextQuesiton=questions[index+1];
    }

    const handlePress=()=>{
            Animated.parallel([
                Animated.timing(current, {
                    toValue:1,
                    duration:400,
                    useNativeDriver:false,
                }),
            Animated.timing(progress, {
                toValue:index+1,
                duration:400,
                useNativeDriver:false
            })
            ]).start(()=>{
                setIndex(index+1);
                current.setValue(0);

                if(index+1>=5){
                    setTimeout(()=>{
                        setRunning(0)
                    }, 500)
                }

            })
    }
    const {width} = Dimensions.get("window");
    const currentAnim=current.interpolate({
        inputRange:[0, 1],
        outputRange:[0, -width]
    });

    const nextAnim=current.interpolate({
        inputRange:[0, 1],
        outputRange:[width, 0]
    });

    const progressAnim=progress.interpolate({
        inputRange:[0, questions.length],
        outputRange:["20%", "100%"],
    });

    const barWidth={
        width:progressAnim
    }

    const translate={
        transform:[
            {translateX:currentAnim}
        ]
    }
    const translateNext={
        transform:[
            {translateX:nextAnim}
        ]
    }


    const renderElement=()=>{
  
            if(running==1){

                return(
                    <View style={styles.mainView}>
                        <Animated.View style={[styles.bar, barWidth]}>
    
                        </Animated.View>
                        <Animated.Text style={[styles.questions, translateNext]}>
                            {nextQuesiton}
                        </Animated.Text>
                        <Animated.Text style={[styles.questions, translate]}>
                            {quesiton}
                        </Animated.Text>
        
                        <TouchableOpacity style={styles.yesBtn} onPress={handlePress}>
                            <Text style={styles.yes}>
                                Yes
                            </Text>
                        </TouchableOpacity>
                        <TouchableOpacity style={styles.noBtn} onPress={handlePress}>
                            <Text style={styles.no}>
                                Yes
                            </Text>
                        </TouchableOpacity>
                    </View>
                )
            }else{
                return(
                    <View style={styles.mainView}>
                        <LottieView 
                            source={require("../assets/done.json")}
                            autoPlay
                            loop={false}
                            style={{
                                width:100,
                                height:100,
                            }}
                        />
                    </View>
                )
            }
        

    }

return(
    renderElement()
)
}

const styles= StyleSheet.create({
    mainView:{
        flex:1,
        justifyContent:"center",
        alignItems:"center"
    },
    bar:{
        backgroundColor:"#fc5c56",
        height:100,
        borderRadius:50,
        position:"absolute",
        top:100,
        //left:-50,
    },
    questions:{
        position:"absolute",
        fontSize:20,
        backgroundColor:"transparent",
        fontSize:20,
        color:"gray"

    },
    yes:{
        color:"white",
        fontFamily:"Avenir",
        fontSize:22,
        fontWeight:"600",
    },
    no:{
        color:"white",
        fontFamily:"Avenir",
        fontSize:22,
        fontWeight:"600",
    },
    yesBtn:{
        width:100,
        height:100,
        borderRadius:50,
        backgroundColor:"red",
        position:"absolute",
        bottom:200,
        left:50,
        justifyContent:"center",
        alignItems:"center"
    },
    noBtn:{
        width:100,
        height:100,
        borderRadius:50,
        backgroundColor:"#fc6c65",
        position:"absolute",
        bottom:200,
        right:50,
        justifyContent:"center",
        alignItems:"center",

    }
})