Анимация в React Native

Animated API - отличный подход в React Native для создания увлекательной анимации.

Он работает, создавая анимированное значение анимации, а затем сопоставляя анимированное значение со стилем, который вы хотите анимировать. Если вы не знакомы с Animated, ознакомьтесь с документацией.

Управлять анимацией с помощью логического значения?

Иногда мы хотим анимировать наш компонент с помощью логического значения, но Animated API не предоставляет его по умолчанию. Это немного неудобно. Например, вы хотите, чтобы на кнопке был эффект плавного появления и исчезновения. Реализация может выглядеть примерно так:

Container (раньше)

// ...
class MyContainer extends React.Component {
  // ... 
  toggleBtn() {
    if (this.state.btnActive) {
      this.setState({ btnActive: false });
      Animated.spring(
        this.state.animatedValue,
        {
          duration: 300,
          toValue: 0,
          friction: 10
        }
      ).start();
    } else {
      this.setState({ btnActive: true });
      Animated.spring(
        this.state.animatedValue,
        {
          duration: 300,
          toValue: 1,
          friction: 10
        }
      ).start();
    }
  } 
  render() {
    return (
      // ...
      <Animated.View
        style={ {
          opacity: this.state.animateValue.interpolate({
            inputRange: [0, 1],
            outputRange: [0, 1]
          }),
        } }
      >
        <Button 
          title="I am a button"
        /> 
      </Animated.View>
      // ...
    );
  }
}

Он не идеален по двум причинам.

  1. Есть две задачи для переключения кнопки
  2. Детали реализации анимации находятся в файле Container.

Анимированные компоненты высшего порядка

Создание animatedHOC может решить проблему. Дополнительный слой может абстрагироваться от деталей для переключения кнопки с анимацией, так что Container больше не заботится о том, как должна быть анимирована кнопка.

animatedHOC обрабатывает всю логику анимации значений анимации следующим образом:

import React, { Component } from ‘react’;
import {
  Animated,
  InteractionManager
} from ‘react-native’;

export default function animatedHOC(WrappedComponent) {
  return class AnimatedCtrl extends Component {
    constructor(props) {
      super(props);
      this.state = {
        active: props.active,
        animateValue: new Animated.Value(0)
      };
    }
    componentWillReceiveProps(nextProps) {
      if (this.state.active === nextProps.active) return;
      Animated.spring(
        this.state.animateValue,
        {
          duration: this.props.duration,
          // Components which are wrapped by the HOC can decide what                
          // animation to implement based on the animateValue 
          // between 0 and 1.
          toValue: nextProps.active ? 1 : 0,
          friction: 10
        }
      ).start();
      this.setState({
        active: nextProps.active
      });
   }
   render() {
     return (<WrappedComponent
       { ...this.props }
       animateValue={ this.state.animateValue }
     />);
  }
};

FadeAnimationBtn получает animateValue реквизит для реализации соответствующей анимации.

Container (после)

import React from ‘react’;
import {
  View,
  Animated,
  Button,
  Text
} from ‘react-native’;
import Button from 'path/to/myButton';
import animatedHOC from ‘../animatedHOC’;
export const FloatBtn = ({
  onPress,
  title,
  animateValue
}) => (
  <Animated.View
    style={ {
      opacity: animateValue.interpolate({
        inputRange: [0, 1],
        outputRange: [0, 1]
      }),
    } }
  >
    <Button
      onPress={ onPress }
      title={ title }
    />
  </Animated.View>
);
export default animatedHOC(FloatBtn);

Обратите внимание, что FadeAnimationBtn должен быть обернут animatedHOC, поскольку FadeAnimationBtn требуетanimatedValue в качестве его свойств.

Container

// ...
import FadeAnimationBtn from 'path/to/FadeAnimationBtn';
class MyContainer extends React.Component {
  // ... 
  toggleBtn() {
    this.setState({
      btnActive: !this.state.btnActive
    });
  } 
  render() {
    return (
      // ...
      <FadeAnimationBtn
        active={ this.state.btnActive }
        title="I am a Button"
      >
      // ...
    );
  }
}

Благодаря такому подходу мы можем реализовать логическую анимацию намного чище. Container заботится только о состоянии кнопки.

Выше только минимальная реализация. Вы можете улучшить AnimatedHOC, добавив InteractionManager или что угодно. Вы также можете добавить другой эффект перехода на основе animatedValue.

Заключение

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