React-Native Animated отстает в недорогих телефонах Android - PullRequest
1 голос
/ 15 октября 2019

Я пытаюсь реализовать анимацию, которая должна выполняться на устройствах iOS и Android.

В iOS производительность кажется удовлетворительной (протестировано с iPhone 6 Plus и выше).

С другой стороны, для некоторых устройств Android анимация отстает.

Вопрос в том, какие действия можно предпринять, чтобы избежать проблемы с производительностью (кроме использования директивы useNativeDriver={true}, которая уже используетсякод)?

Код такой:

import * as React from 'react';
import {
  Animated,
  ImageBackground,
  StyleSheet,
  Image,
  Easing,
  TextInput,
  View,
  Text,
} from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import backImg from './background.png';

const c_initial_coordinate_left = 100;
const c_initial_coordinate_top = 100;

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.spaceAnimatedTranslations = new Animated.ValueXY();
    this.spaceAnimatedTranslations2 = new Animated.ValueXY();
    this.spaceAnimatedTranslations3 = new Animated.ValueXY();
    this.spaceAnimatedTranslations4 = new Animated.ValueXY();
    this.spaceAnimatedTranslations5 = new Animated.ValueXY();
    this.spaceAnimatedTranslations.addListener(value => (this.spaceAnimatedTranslations_value = value));
    this.spaceAnimatedTranslations2.addListener(value => (this.spaceAnimatedTranslations_value2 = value));
    this.spaceAnimatedTranslations3.addListener(value => (this.spaceAnimatedTranslations_value3 = value));
    this.spaceAnimatedTranslations4.addListener(value => (this.spaceAnimatedTranslations_value4 = value));
    this.spaceAnimatedTranslations5.addListener(value => (this.spaceAnimatedTranslations_value5 = value));
    this._animatedStyle = {transform: [{ translateX: this.spaceAnimatedTranslations.x }, { translateY: this.spaceAnimatedTranslations.y },],};
    this._animatedStyle2 = {transform: [{ translateX: this.spaceAnimatedTranslations2.x }, { translateY: this.spaceAnimatedTranslations2.y },],};
    this._animatedStyle3 = {transform: [{ translateX: this.spaceAnimatedTranslations3.x }, { translateY: this.spaceAnimatedTranslations3.y },],};
    this._animatedStyle4 = {transform: [{ translateX: this.spaceAnimatedTranslations4.x }, { translateY: this.spaceAnimatedTranslations4.y },],};
    this._animatedStyle5 = {transform: [{ translateX: this.spaceAnimatedTranslations5.x }, { translateY: this.spaceAnimatedTranslations5.y },],};
  }

  onSpaceMove(event) {
    let l_panTranslateX = event.nativeEvent.translationX;
    let l_panTranslateY = event.nativeEvent.translationY;
    let l_panStartX = event.nativeEvent.x - event.nativeEvent.translationX;
    let l_panStartY = event.nativeEvent.y - event.nativeEvent.translationY;

    let l_animationsArray = new Array();
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations2.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations3.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations4.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations5.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations2.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations3.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations4.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
    l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations5.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
    Animated.parallel(l_animationsArray).start();

    this.debug_message = `\n
      event.nativeEvent.translationX: ${Math.floor(
        event.nativeEvent.translationX
      )}
      event.nativeEvent.translationY: ${Math.floor(
        event.nativeEvent.translationY
      )}
      event.nativeEvent.absoluteX: ${Math.floor(event.nativeEvent.absoluteX)}
      event.nativeEvent.absoluteY: ${Math.floor(event.nativeEvent.absoluteY)}
      event.nativeEvent.x: ${Math.floor(event.nativeEvent.x)}
      event.nativeEvent.y: ${Math.floor(event.nativeEvent.y)}
      this.spaceAnimatedTranslations_value.x: ${Math.floor(
        this.spaceAnimatedTranslations_value.x
      )}
      this.spaceAnimatedTranslations_value.y: ${Math.floor(
        this.spaceAnimatedTranslations_value.y
      )}
      this.gestureStartedX: ${Math.floor(this.gestureStartedX)}
      this.gestureStartedY: ${Math.floor(this.gestureStartedY)}
        `;
    this.forceUpdate();
  }

  onSpaceMoveCompleted(event) {
    if (event.nativeEvent.state === State.BEGAN) {
      this.spaceAnimatedTranslations.flattenOffset();
      this.spaceAnimatedTranslations2.flattenOffset();
      this.spaceAnimatedTranslations3.flattenOffset();
      this.spaceAnimatedTranslations4.flattenOffset();
      this.spaceAnimatedTranslations5.flattenOffset();
      this.spaceAnimatedTranslations.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
      this.spaceAnimatedTranslations2.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
      this.spaceAnimatedTranslations3.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
      this.spaceAnimatedTranslations4.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
      this.spaceAnimatedTranslations5.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top ),});
      this.gestureStartedX = event.nativeEvent.absoluteX;
      this.gestureStartedY = event.nativeEvent.absoluteY;
    }
    if (event.nativeEvent.state === State.END) {
      this.onSpaceMove(event);
    }
  }

  render() {
    return (
      <ImageBackground source={backImg} style={{ flex: 1 }}>
        {this.debug_message ? (
          <Text style={{ color: 'white' }}>{this.debug_message}</Text>
        ) : (
          undefined
        )}

        <PanGestureHandler
          key={`test`}
          onGestureEvent={e => this.onSpaceMove(e)}
          onHandlerStateChange={e => this.onSpaceMoveCompleted(e)}>
          <Animated.View
            ref={ref => {
              this.testAnimatedView = ref;
            }}
            style={[styles._animatable_view, this._animatedStyle]}
            useNativeDriver={true}>
            <View style={styles._box_content}>
              <Text
                style={{
                  width: '100%',
                  height: '100%',
                  fontSize: 15,
                  textAlign: 'center',
                  textAlignVertical: 'center',
                  borderRadius: 20,
                }}
                editable={false}
                ref={ref => {
                  this.textInputRef = ref;
                }}
              >
              {'Master (1) (DRAG THIS ONE)'}
              </Text>
            </View>
          </Animated.View>
        </PanGestureHandler>
          <Animated.View
            ref={ref => {
              this.testAnimatedView2 = ref;
            }}
            style={[styles._animatable_view2, this._animatedStyle2]}
            useNativeDriver={true}>
            <View style={styles._box_content}>
              <Text
                style={{
                  width: '100%',
                  height: '100%',
                  fontSize: 15,
                  textAlign: 'center',
                  textAlignVertical: 'center',
                  borderRadius: 20,
                }}
                editable={false}
                ref={ref => {
                  this.textInputRef = ref;
                }}
              >
              {'Slave (2) '}
              </Text>
            </View>
          </Animated.View>
          <Animated.View
            ref={ref => {
              this.testAnimatedView3 = ref;
            }}
            style={[styles._animatable_view3, this._animatedStyle3]}
            useNativeDriver={true}>
            <View style={styles._box_content}>
              <Text
                style={{
                  width: '100%',
                  height: '100%',
                  fontSize: 15,
                  textAlign: 'center',
                  textAlignVertical: 'center',
                  borderRadius: 20,
                }}
                editable={false}
                ref={ref => {
                  this.textInputRef = ref;
                }}
              >
              {'Slave (3) '}
              </Text>
            </View>
          </Animated.View>
          <Animated.View
            ref={ref => {
              this.testAnimatedView4 = ref;
            }}
            style={[styles._animatable_view4, this._animatedStyle4]}
            useNativeDriver={true}>
            <View style={styles._box_content}>
              <Text
                style={{
                  width: '100%',
                  height: '100%',
                  fontSize: 15,
                  textAlign: 'center',
                  textAlignVertical: 'center',
                  borderRadius: 20,
                }}
                editable={false}
                ref={ref => {
                  this.textInputRef = ref;
                }}
              >
              {'Slave (4) '}
              </Text>
            </View>
          </Animated.View>
          <Animated.View
            ref={ref => {
              this.testAnimatedView5 = ref;
            }}
            style={[styles._animatable_view5, this._animatedStyle5]}
            useNativeDriver={true}>
            <View style={styles._box_content}>
              <Text
                style={{
                  width: '100%',
                  height: '100%',
                  fontSize: 15,
                  textAlign: 'center',
                  textAlignVertical: 'center',
                  borderRadius: 20,
                }}
                editable={false}
                ref={ref => {
                  this.textInputRef = ref;
                }}
              >
              {'Slave (5) '}
              </Text>
            </View>
          </Animated.View>
      </ImageBackground>
    );
  }
}

const styles = StyleSheet.create({
  _animatable_view: {
    flex: 1,
    width: 250,
    height: 50,
    top: c_initial_coordinate_top,
    left: c_initial_coordinate_left,
    position: 'absolute',
    backgroundColor: '#ABC',
    alignItems: 'center',
    justifyContent: 'center',
    borderColor: 'gainsboro',
    borderWidth: 2,
  },
  _animatable_view2: {
    flex: 1,
    width: 250,
    height: 50,
    top: c_initial_coordinate_top + 100,
    left: c_initial_coordinate_left,
    position: 'absolute',
    backgroundColor: '#ABC',
    alignItems: 'center',
    justifyContent: 'center',
    borderColor: 'gainsboro',
    borderWidth: 2,
  },
  _animatable_view3: {
    flex: 1,
    width: 250,
    height: 50,
    top: c_initial_coordinate_top + 200,
    left: c_initial_coordinate_left,
    position: 'absolute',
    backgroundColor: '#ABC',
    alignItems: 'center',
    justifyContent: 'center',
    borderColor: 'gainsboro',
    borderWidth: 2,
  },
  _animatable_view4: {
    flex: 1,
    width: 250,
    height: 50,
    top: c_initial_coordinate_top + 300,
    left: c_initial_coordinate_left,
    position: 'absolute',
    backgroundColor: '#ABC',
    alignItems: 'center',
    justifyContent: 'center',
    borderColor: 'gainsboro',
    borderWidth: 2,
  },
  _animatable_view5: {
    flex: 1,
    width: 250,
    height: 50,
    top: c_initial_coordinate_top + 400,
    left: c_initial_coordinate_left,
    position: 'absolute',
    backgroundColor: '#ABC',
    alignItems: 'center',
    justifyContent: 'center',
    borderColor: 'gainsboro',
    borderWidth: 2,
  },
  _box_content: {
    flex: 1,
    height: '100%',
    width: '100%',
    borderRadius: Math.min(this.rectangleHeight, this.rectangleWidth) / 2,
    alignItems: 'center',
    justifyContent: 'center',
    borderColor: 'gainsboro',
    borderWidth: 2,
    opacity: 0.9,
  },
});

Это можно увидеть в действии в этой закуске: https://snack.expo.io/@mehmetkaplan/movetextwithgesturesingle

Дополнительно, чтобы чувствоватьИз-за проблемы с производительностью я создал еще одну закуску, которая просто оживляет 5 объектов одновременно. Если вы запускаете это на устройствах с низким уровнем Android, вы можете почувствовать проблему с производительностью: https://snack.expo.io/@mehmetkaplan/movetextwithgesturemulti

Ответы [ 2 ]

2 голосов
/ 24 октября 2019

Я также хотел бы добавить, что useNativeDriver является НЕ опорой, которую вы добавляете в компоненты! Использование useNativeDriver={true} здесь абсолютно ничего ! Из документов правильное использование useNativeDriver находится в Animated.[function] s. Используя приведенный выше код, правильное использование будет находиться здесь:

Animated.timing(this.spaceAnimatedTranslations5.x, {
    toValue: event.nativeEvent.translationX,
    duration: 0,
    easing: Easing.linear
})

Вам потребуется добавить его в качестве другого параметра во втором аргументе в качестве свойства объекта параметров.

Animated.timing([Animated Value], {options})

Animated.timing(this.spaceAnimatedTranslations5.x, {
    toValue: event.nativeEvent.translationX,
    duration: 0,
    easing: Easing.linear,
    useNativeDriver: true
})

Другие анимированные функции, с которыми вы можете использовать его: Animated.event или Animated.interpolate.

Анимации очень трудоемки, поэтому убедитесь, что вы используете useNativeDriverкогда это возможно и где применимо.

1 голос
/ 20 октября 2019

Для будущих посетителей:

Есть 2 метода, которые я придумала, но все же для бюджетных телефонов производительность не настолько удовлетворительная:

  1. Естьпрактически нет необходимости иметь 5 разных анимаций для этого кода, поэтому в массив анимаций помещается только this.spaceAnimatedTranslations.

  2. Другой подход заключается в том, что если вы хотите, чтобы объекты сразу же следовали за перетаскиванием,использование анимации на самом деле не нужно. Скорее прямое обновление значения через .setValue более логично. Этот код можно увидеть в действии в этот перекус .

Для элемента 2 изменение здесь:

//let l_animationsArray = new Array();
//l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
//l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
//Animated.parallel(l_animationsArray).start();
this.spaceAnimatedTranslations.setValue({x: l_panTranslateX, y: l_panTranslateY});
...