Sunday, May 13, 2018

Having a rendering issue with React-Native Animation

Leave a Comment

I am having trouble with an animation.I attempting to flip the card with two different views. I am also trying to create a scrolling effect when the user scrolls between two different cards. When the code is combined in the manner down below, it creating a bug that I cannot squash. I included an image to give a visual representation of my issue.

I appreciate any help.

:

My Life Cycle method:

componentWillMount() {     this.animatedValue = new Animated.Value(0);     this.value = 0;     this.animatedValue.addListener(({ value }) => {       this.value = value;       this.setState({ value });     });     this.frontInterpolate = this.animatedValue.interpolate({       inputRange: [0, 180],       outputRange: ['0deg', '180deg']     });     this.backInterpolate = this.animatedValue.interpolate({       inputRange: [0, 180],       outputRange: ['180deg', '360deg']     });   } } 

This animation that is used to produce the flip animation:

  flipCard() {      if (this.value >= 90) {       this.setState({         isWaiting: true       });       Animated.spring(this.animatedValue, {         toValue: 0,         friction: 8,         tension: 10       }).start(() => {         this.setState({           isWaiting: false         });       });     } else {       this.setState({         isWaiting: true       });       Animated.spring(this.animatedValue, {         toValue: 180,         friction: 8,         tension: 10       }).start(() => {         this.setState({ isWaiting: false });       });     }   } 

This is View that is being flipped via the flipCard function. If you see in one of the views, there is a function called transitionAnimation. That is used to produce the scrolling effect.

 <View style={styles.scrollPage}>         <View>           <Animated.View               style={[                  frontAnimatedStyle,                    styles.screen,                     this.transitionAnimation(index)                      ]}                    >                    <Text style={styles.text}>{question.question}</Text>          </Animated.View>              <Animated.View                style={[                   styles.screen,                   backAnimatedStyle,                     styles.back,                      this.transitionAnimation(index)                     ]}                     >                     <Text style={styles.text}>{question.answer}</Text>                  </Animated.View> 

The transitionAnimation:

transitionAnimation = index => {     if (!this.state.isWaiting) {       return {         transform: [           { perspective: 800 },           {             scale: xOffset.interpolate({               inputRange: [                 (index - 1) * SCREEN_WIDTH,                 index * SCREEN_WIDTH,                 (index + 1) * SCREEN_WIDTH               ],               outputRange: [0.25, 1, 0.25]             })           },           {             rotateX: xOffset.interpolate({               inputRange: [                 (index - 1) * SCREEN_WIDTH,                 index * SCREEN_WIDTH,                 (index + 1) * SCREEN_WIDTH               ],               outputRange: ['45deg', '0deg', '45deg']             })           },           {             rotateY: xOffset.interpolate({               inputRange: [                 (index - 1) * SCREEN_WIDTH,                 index * SCREEN_WIDTH,                 (index + 1) * SCREEN_WIDTH               ],               outputRange: ['-45deg', '0deg', '45deg']             })           }         ]       };     }   }; 

My render function:

render() {     const { flashcards } = this.state;      return (       <View style={styles.container}>         <View           style={{             alignItems: 'flex-end',             marginTop: 10           }}         >           <Progress.Circle             size={70}             showsText             progress={this.state.timer}             formatText={text => {               return (this.state.timer * 100).toFixed(0);             }}           />         </View>         <Animated.ScrollView           scrollEventThrottle={16}           onScroll={Animated.event(             [{ nativeEvent: { contentOffset: { x: xOffset } } }],             { useNativeDriver: true }           )}           horizontal           pagingEnabled           style={styles.scrollView}         >           {this.state.flashcards && this.renderCard()}         </Animated.ScrollView>       </View>     );   } } 

Animation not working properly

I also created a snack bar where you can look at the problem. https://snack.expo.io/@louis345/flaschards

1 Answers

Answers 1

You have lots of issues:

  1. The main problem is because you don't store the state of each card properly (if it is flipped or not). For example you can add flippedCards Array or Set to your state and update it each time you flip a card so that it could render properly after setState is called when animation ends, and for proper rendering of other cards that weren't flipped.

  2. You render and animate (flip and transition) all cards at a time, but you should render only three cards (current and neighbours), and you should flip only current card.

  3. Performance issues: you create transition styles and other functions on each render, that makes your render very slow.

  4. Other code that should be refactored.

I fixed 1 and 3 problems and refactored a bit. 2 is up to you:

import React, { Component } from 'react'; import { Animated, Dimensions, StyleSheet, Text, View, TouchableOpacity, TouchableWithoutFeedback } from 'react-native'; import { EvilIcons, MaterialIcons } from '@expo/vector-icons';  const SCREEN_WIDTH = Dimensions.get('window').width;  export default class App extends Component {   constructor(props) {     super(props);      const flashcards = ['konichiwa','hi','genki desu','how are you'];      this.state = {       flashcards,       flipped: flashcards.map(() => false),       flipping: false     };      this.flipValue = new Animated.Value(0);      this.frontAnimatedStyle = {       transform: [{         rotateY: this.flipValue.interpolate({           inputRange: [0, 1],           outputRange: ['0deg', '180deg']         })       }]     };      this.backAnimatedStyle = {       transform: [{         rotateY: this.flipValue.interpolate({           inputRange: [0, 1],           outputRange: ['180deg', '360deg']         })       }]     };      let xOffset = new Animated.Value(0);     this.onScroll = Animated.event(       [{ nativeEvent: { contentOffset: { x: xOffset } } }],       { useNativeDriver: false }     );      this.transitionAnimations = this.state.flashcards.map((card, index) => ({       transform: [         { perspective: 800 },         {           scale: xOffset.interpolate({             inputRange: [               (index - 1) * SCREEN_WIDTH,               index * SCREEN_WIDTH,               (index + 1) * SCREEN_WIDTH             ],             outputRange: [0.25, 1, 0.25]           })         },         {           rotateX: xOffset.interpolate({             inputRange: [               (index - 1) * SCREEN_WIDTH,               index * SCREEN_WIDTH,               (index + 1) * SCREEN_WIDTH             ],             outputRange: ['45deg', '0deg', '45deg']           })         },         {           rotateY: xOffset.interpolate({             inputRange: [               (index - 1) * SCREEN_WIDTH,               index * SCREEN_WIDTH,               (index + 1) * SCREEN_WIDTH             ],             outputRange: ['-45deg', '0deg', '45deg']           })         }       ]     }));   }    render() {     return (       <View style={styles.container}>         <Animated.ScrollView           scrollEnabled={!this.state.flipping}           scrollEventThrottle={16}           onScroll={this.onScroll}           horizontal           pagingEnabled           style={styles.scrollView}>           {this.state.flashcards.map(this.renderCard)}         </Animated.ScrollView>       </View>     );   }    renderCard = (question, index) => {     const isFlipped = this.state.flipped[index];      return (       <TouchableWithoutFeedback key={index} onPress={() => this.flipCard(index)}>         <View>            <View style={styles.scrollPage}>             <View>               {(this.state.flipping || !isFlipped) && <Animated.View                 style={[                   this.state.flipping ? this.frontAnimatedStyle : this.transitionAnimations[index],                   styles.screen                 ]}               >                 <Text style={styles.text}>{this.state.flashcards[index]}</Text>               </Animated.View>}                {(this.state.flipping || isFlipped) && <Animated.View                 style={[                   styles.screen,                   this.state.flipping ? this.backAnimatedStyle : this.transitionAnimations[index],                   this.state.flipping && styles.back                 ]}               >                 <Text style={styles.text}>{this.state.flashcards[index+1]}</Text>               </Animated.View>}             </View>           </View>            <View style={styles.iconStyle}>             <TouchableOpacity>               <EvilIcons name="check" size={80} color={'#5CAF25'} />             </TouchableOpacity>             <TouchableOpacity>               <MaterialIcons name="cancel" size={70} color={'#b71621'} />             </TouchableOpacity>           </View>          </View>       </TouchableWithoutFeedback>     );   }    flipCard = index => {     if (this.state.flipping) return;      let isFlipped = this.state.flipped[index];     let flipped = [...this.state.flipped];     flipped[index] = !isFlipped;      this.setState({       flipping: true,       flipped     });      this.flipValue.setValue(isFlipped ? 1: 0);     Animated.spring(this.flipValue, {       toValue: isFlipped ? 0 : 1,       friction: 8,       tension: 10     }).start(() => {       this.setState({ flipping: false });     });   } }  const styles = StyleSheet.create({   container: {     backgroundColor:'red',     flex: 1,     flexDirection: 'column',     justifyContent: 'space-between'   },   scrollView: {     flexDirection: 'row',     backgroundColor: 'black'   },   scrollPage: {     width: SCREEN_WIDTH,     padding: 20   },   screen: {     height: 400,     justifyContent: 'center',     alignItems: 'center',     borderRadius: 25,     backgroundColor: 'white',     width: SCREEN_WIDTH - 20 * 2,     backfaceVisibility: 'hidden'   },   text: {     fontSize: 45,     fontWeight: 'bold'   },   iconStyle: {     flexDirection: 'row',     justifyContent: 'center'   },   back: {     position: 'absolute',     top: 0,     backfaceVisibility: 'hidden'   } }); 

At least now it works fine.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment