[Перевод] React Native: Создание анимированного поля ввода с помощью Animated API

Всем привет! Сегодня делимся с вами познавательным материалом, перевод которого подготовлен специально для студентов курса «ReactJS/React Native-разработчик».

wv4mmtxb4luqqwinjrbsrazou3e.png

Итак, начнем.


Все мы видели такие поля для ввода:

nf3igplodn7ujtklxto_qqi9lm4.gif

Надпись большая и выглядит как плейсхолдер, пока вы не наведете фокус на ввод. Она станет меньше и поднимется вверх.

Выглядит великолепно. Гладко. Безупречно.

А еще кажется, что только опытный разработчик может так сделать, не правда ли?
Ну, возможно, это и было так до появления React Native, в те времена, когда люди жили в пещерах и творили всякую дичь. Но это осталось в прошлом.

Вы можете посмотреть видео или продолжить читать. Все зависит от того, что вам больше по душе.

Чего мы добиваемся?


Есть два варианта того, с чем мы имеем дело.
Первый — когда на поле ввода нет фокуса.

5wjni-y8vhqpgw3zeioq3fvoh44.png

Надпись появляется внутри поля ввода, а ее размер равен размеру текстового поля. Цвет тусклый. Это очень похоже на свойство placeholder.
И второй вариант, когда на поле ввода есть фокус.

vammdeeeyzjqchmnmdxfhgd7n1u.png

Надпись оказывается над полем ввода, ее размер меньше, и цвет отличается от цвета вводимого текста.

Простейшая реализация


Наконец-то мы можем приступить к работе. Пока без каких-либо анимаций.
Как оказалось, у нас есть два состояния UI:

  1. На поле нет фокуса и надпись внутри поля.
  2. На поле есть фокус, надпись над полем ввода.

of-n9tgpa9lwq4ey_ibjc0tzz7q.png

По факту, мы могли бы хранить состояние того, есть фокус на поле или нет. Затем в зависимости от этого состояния, мы могли бы выбрать, где размещать надпись и какие стили к ней применить.

Поскольку надпись должна находиться в разных местах, и мы не хотим, чтобы она влияла на размещение компонентов, мы будем позиционировать ее абсолютно. Чтобы убедиться, что места для нее хватит, придется добавить в wrapping view отступ сверху.

class FloatingLabelInput extends Component {
  state = {
    isFocused: false,
  };

  handleFocus = () => this.setState({ isFocused: true });
  handleBlur = () => this.setState({ isFocused: false });

  render() {
    const { label, ...props } = this.props;
    const { isFocused } = this.state;
    const labelStyle = {
      position: 'absolute',
      left: 0,
      top: !isFocused ? 18 : 0,
      fontSize: !isFocused ? 20 : 14,
      color: !isFocused ? '#aaa' : '#000',
    };
    return (
      
        
          {label}
        
        
      
    );
  }
}

После предыдущих шагов, мы можем достичь следующего:

https://snack.expo.io/Sk006AbdW? session_id=snack-session-JRMksbYK3

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

Почему бы не использовать placeholder?


Конечно, использование свойства TextInput плейсхолдера кажется заманчивым. Однако это не сработает, поскольку мы хотим контролировать как, когда и где отображается надпись.

Вместо этого, мы хотим, чтобы надпись находилась внутри текстового поля, когда на нем фокус. А еще мы хотим, чтобы она сдвигалась вверх, когда на поле ввода появляется фокус, и этого можно достичь только, если мы работаем с одним и тем же элементом.


Как насчет анимации?


На самом деле осталась самая легкая часть.

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

Из этого руководства мы можем выделить следующее:

  • Animated.Value будет означать, есть фокус на поле (1) или нет (0);
  • Мы постепенно будем менять число на (1) при фокусировке и на (0) в противном случае;
  • В виде этого числа мы будем отражать стиль надписи: в (0) и (1) мы определим стили, а React Native автоматически вычислит и применит промежуточные стили. И даже цвета.


Реализовать это все несложно.

Animated.Value нам нужно будет инициализировать в componentWillMount.

componentWillMount() {
  this._animatedIsFocused = new Animated.Value(0);
}

Затем, поскольку значение этого числа должно основываться на том, есть ли фокус на поле ввода или нет, и поскольку у нас уже есть этот бит информации о состоянии, мы можем добавить функцию componentDidUpdate, которая будет менять это число в зависимости от this.state:

componentDidUpdate() {
  Animated.timing(this._animatedIsFocused, {
    toValue: this.state.isFocused ? 1 : 0,
    duration: 200,
  }).start();
}

Теперь, чтобы отразить стиль надписи в таких терминах, нам понадобится внести всего два изменения:

Меняем на Animated.Text.

Вместо того, чтобы использовать условия для определения стилей, определяйте их следующим образом:

const labelStyle = {
  position: 'absolute',
  left: 0,
  top: this._animatedIsFocused.interpolate({
    inputRange: [0, 1],
    outputRange: [18, 0],
  }),
  fontSize: this._animatedIsFocused.interpolate({
    inputRange: [0, 1],
    outputRange: [20, 14],
  }),
  color: this._animatedIsFocused.interpolate({
    inputRange: [0, 1],
    outputRange: ['#aaa', '#000'],
  }),
};

https://snack.expo.io/Hk8VCR-dZ? session_id=snack-session-AJ4vulSVw

Еще кое-что


Если в демо выше вы попробуете что-нибудь ввести, а затем убрать фокус с поля ввода, то увидите нечто странное.

kvbac0dg8pcdu7qq2ucgkql76cs.png

К счастью, это довольно просто исправить. Нам нужно всего лишь поменять две строчки в коде.

Мы хотим проверить, пустое ли поле ввода и изменить состояние на «unfocused», только если оба следующих условия выполняются:

  • Значение поля ввода пустое;
  • На поле нет фокуса.

В противном случае, мы хотим, чтобы применялся стиль «focused», а замещающая надпись поднималась вверх.

Теперь, когда мы отслеживаем состояние поля ввода, мы можем легко получить доступ к его значению с помощью this.props.

6fvgfrfqx2lsx_qpg8zyunqrtim.gif

https://snack.expo.io/ByZBAC-dZ? session_id=snack-session-YNZSqhqOC

На этом всё. До встречи на бесплатном вебинаре.

© Habrahabr.ru