Имея проблему рендеринга с реактивной анимацией

У меня возникают проблемы с анимацией. Я пытаюсь перевернуть карту двумя разными видами. Я также пытаюсь создать эффект прокрутки, когда пользователь прокручивает две разные карты. Когда код объединяется ниже, он создает ошибку, которую я не могу раздавить. Я включил изображение для визуального представления моей проблемы.

Я ценю любую помощь.

:

Мой жизненный цикл:

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']
    });
  }
}

Эта анимация, которая используется для создания флип-анимации:

  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 });
      });
    }
  }

Это вид, который переводится через функцию flipCard. Если вы видите в одном из представлений, есть функция, называемая transitionAnimation. Это используется для создания эффекта прокрутки.

 <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>

Переход:

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']
            })
          }
        ]
      };
    }
  };

Моя функция рендеринга:

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

Я также создал снэк-бар, где вы можете посмотреть на проблему. https://snack.expo.io/@louis345/flaschards

Ответы

Ответ 1

У вас много проблем:

  1. Основная проблема заключается в том, что вы не сохраняете состояние каждой карты должным образом (если оно перевернуто или нет). Например, вы можете добавить flippedCards Array или Set в свое состояние и обновлять его каждый раз, когда вы переворачиваете карту, чтобы она могла отображаться правильно после setState при завершении анимации и для правильного отображения других карт, которые не были перевернуты.

  2. Вы производите визуализацию и анимацию (переключение и переход) по всем картам за раз, но вы должны отображать только три карты (текущий и соседний), и вам нужно перевернуть только текущую карту.

  3. Проблемы с производительностью: вы создаете стили перехода и другие функции для каждого рендеринга, что делает рендер очень медленным.

  4. Другой код, который необходимо реорганизовать.

Я исправил 1 и 3 проблемы и немного переработал. 2 зависит от вас:

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'
  }
});

По крайней мере, сейчас это прекрасно работает.