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

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

Настраивать

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

Поскольку нам нужно где-то хранить текстовое значение, нам нужно создать какое-то состояние.

Мы сделаем это, используя синтаксис свойств класса, как показано ниже. Это создаст наш объект состояния для этого экземпляра и создаст ключ с именем value с пустой строкой.

export default class animated_notification extends Component {
  state = {
    value: "",
  };
}

Далее наша функция рендеринга.

render() {
    return (
      <View style={styles.container}>
        <View>
          <TextInput
            style={styles.input}
            value={this.state.value}
            onChangeText={value => this.setState({ value })}
          />
          <TouchableOpacity onPress={this.handlePress}>
            <View style={styles.button}>
              <Text style={styles.buttonText}>Show Notification</Text>
            </View>
          </TouchableOpacity>
        </View>
      </View>
    );
  }

Здесь мы установили простой контейнер, который центрирует все (мы перейдем к стилизации через секунду). Внутри нашего контейнера у нас есть упаковка View вокруг двух других компонентов.

Первый - это наш TextInput. Здесь мы вводим наше уведомление. Мы передаем наш this.state.value, который мы настроили выше. Затем используйте обратный вызов onChangeText, чтобы обновить состояние value on. Это стандартный метод контролируемого ввода в React. Каждый раз, когда state изменяется, TextInput обновляет свое значение.

Наконец-то у нас есть TouchableOpacity. Это дает нам onPress обратный вызов, чтобы отобразить наше уведомление. Мы добавляем обертку View и немного Text, чтобы можно было стилизовать нашу кнопку.

Он вызывает нашу handlePress функцию, которая также использует синтаксис защиты классов. Это позволяет нам автоматически привязать нашу функцию к экземпляру компонента.

handlePress = () => { };

Теперь о нашей стилизации. Вот наш первоначальный стиль.

container занимает весь экран с flex: 1 и центрирует наши элементы по горизонтали и вертикали.

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

buttonText изменяет наш текст на белый и центрирует текст по центру нашей кнопки.

input Мы устанавливаем некоторые базовые размеры (ширина / высота), некоторые отступы, которые будут отступать от нашего текста, и, наконец, добавляем светлую границу.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  button: {
    backgroundColor: "tomato",
    padding: 15,
    marginTop: 10,
  },
  buttonText: {
    color: "#FFF",
    textAlign: "center",
  },
  input: {
    width: 250,
    height: 40,
    padding: 5,
    borderWidth: 1,
    borderColor: "#CCC",
  },
});

Вся наша установка по умолчанию выглядит так.

export default class animated_notification extends Component {
  state = {
    value: "",
  };
  handlePress = () => {

  };
  render() {
    return (
      <View style={styles.container}>
        <View>
          <TextInput
            style={styles.input}
            value={this.state.value}
            onChangeText={value => this.setState({ value })}
          />
          <TouchableOpacity onPress={this.handlePress}>
            <View style={styles.button}>
              <Text style={styles.buttonText}>Show Notification</Text>
            </View>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  button: {
    backgroundColor: "tomato",
    padding: 15,
    marginTop: 10,
  },
  buttonText: {
    color: "#FFF",
    textAlign: "center",
  },
  input: {
    width: 250,
    height: 40,
    padding: 5,
    borderWidth: 1,
    borderColor: "#CCC",
  },
});

Уведомление о настройке

Нам нужно настроить другую переменную состояния, чтобы удерживать текст нашего уведомления и очистить наш value. Итак, давайте отредактируем наш state, чтобы он выглядел как

state = {
    value: "",
    notification: "",
  };

Затем нам нужно изменить handlePress, чтобы очистить value и поместить его в новое состояние notification.

handlePress = () => {
    this.setState({
      value: "",
      notification: this.state.value,
    });
  };

Пришло время определить, как будет выглядеть наше уведомление. Мы будем анимировать уведомление, а это значит, что нам нужно использовать Animated.View, а не просто View.

Мы устанавливаем Animated.View со ссылкой like<Animated.View ref={notification => (this._notification = notification)}>, потому что в конечном итоге нам нужно использовать функцию measure для ссылки, чтобы мы действительно могли создать динамическую анимацию на основе высоты уведомления. Это позволит нам сделать гибкий компонент уведомления, а не указывать конкретную высоту.

Последняя часть - это текст, который будет отображать наш this.state.notification.

<View style={styles.container}>
        <Animated.View style={[styles.notification]} ref={notification => (this._notification = notification)}>
          <Text style={styles.notificationText}>
              {this.state.notification}
            </Text>
        </Animated.View>
        // TextInput and button code
      </View>

Нам нужно добавить наш стиль notification и notificationText.

notification: {
    position: "absolute",
    paddingHorizontal: 7,
    paddingVertical: 15,
    left: 0,
    top: 0,
    right: 0,
    backgroundColor: "tomato",
  },
  notificationText: {
    color: "#FFF",
  },

notificationText просто добавляет белый цвет. notification - это то, что будет позиционировать и стилизовать наше уведомление. Мы используем position: "absolute", поэтому стиль container не влияет на наше уведомление. Мы добавляем отступ, чтобы текст нашего внутреннего уведомления не располагался прямо на краю экрана.

Наконец, мы используем left: 0, right: 0, top: 0, чтобы расположить вид сверху и поперек экрана. Это изменит размер окна уведомлений до краев его родительского контейнера. В нашем случае родительский контейнер - это container представление, занимающее весь экран. Таким образом, наше представление уведомлений будет растягиваться по экрану.

Скрыть, чтобы начать

Мы хотим, чтобы уведомление начиналось скрытым, а анимация - видимым. Это означает, что нам нужно настроить анимированное значение, чтобы контролировать непрозрачность нашего представления уведомлений. Мы можем добавить это к нашему состоянию.

state = {
    value: "",
    notification: "",
    opacity: new Animated.Value(0),
  };

Затем нам нужно настроить динамический стиль для применения к нашему Animated.View. Мы сделаем это в верхней части нашей функции рендеринга.

render() {
  const notificationStyle = {
    opacity: this.state.opacity,
  };
}

Затем передайте его нашему Animated.View, используя нотацию стиля массива. Это позволяет нам применять к представлению несколько стилей.

<Animated.View style={[styles.notification, notificationStyle]}>

Динамическое измерение

Когда вы получаете доступ к ref на View, у него есть несколько полезных функций. Одна из них - функция measure. Нам нужно было обязательно позвонить getNode на наш ref. Animated.View является оболочкой для обычного View и предоставляет функцию getNode для получения доступа к внутреннему View, у которого есть наша measure функция, которая нам нужна.

Наша цель - получить динамичный height наш взгляд.

this.setState(
      {
        value: "",
        notification: this.state.value,
      },
      () => {
        this._notification.getNode().measure((x, y, width, height, pageX, pageY) => {

        })
      } 
)

Синтаксис здесь может показаться странным. Что происходит, мы звоним setState. Вторая функция, которую мы передаем в setState, эквивалентна componentDidUpdate. Это означает, что текст notification был официально обработан и обновлен. Это означает, что когда мы измеряем представление уведомления, оно будет возвращать точные значения.

measure возвращает 6 разных аргументов, но нас интересует только высота.

Анимировать в

Нам нужно контролировать смещение нашего представления уведомлений. Это всегда будет установлено на высоту вида прямо перед его анимацией.

Итак, мы изменим наше состояние, чтобы оно выглядело как

state = {
    value: "",
    notification: "",
    opacity: new Animated.Value(0),
    offset: new Animated.Value(0),
  };

Теперь наш handlePress будет настроен для анимации нашего уведомления в.

Когда у нас есть измерение высоты нашего уведомления, нам нужно установить его как отрицательное смещение. Мы можем сделать это, используя свойство setValue в нашем offset.

this.state.offset.setValue(height * -1);

Как только мы это сделаем, мы можем сделать параллельную анимацию, используя Animated.parallel. Это позволит нам делать несколько анимаций одновременно. Для нас это означает анимацию в наших opacity и offset.

Animated.parallel([
  Animated.timing(this.state.opacity, {
    toValue: 1,
    duration: 300,
  }),
  Animated.timing(this.state.offset, {
    toValue: 0,
    duration: 300,
  }),
]).start()

Вы можете видеть, что Animated.parallel принимает массив анимаций. Наш первый будет использовать Animated.timing для анимации нашего opacity в 1. Его начальное значение было установлено на 0 в нашем состоянии opacity: new Animated.Value(0),. Когда непрозрачность равна 0, вид не отображается.

Следующим шагом будет также преобразование нашего offset в 0. Мы выясним, почему ниже.

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

const notificationStyle = {
      opacity: this.state.opacity,
      transform: [
        {
          translateY: this.state.offset,
        },
      ],
    };

Когда мы устанавливаем offset анимацию на -height, это отрицательно перемещает вид по оси Y. Это означает, что он переместится вверх по экрану на установленную нами сумму. Таким образом, в этом случае он переместит его на точную высоту представления, чтобы он не был виден. Это заставит наше уведомление выглядеть так, как будто оно появляется за пределами экрана.

Если для нашего offset изначально установлено значение 0, это делает уведомление видимым в исходной позиции. Вот почему мы делаем анимацию Animated.timing на 0.

Анимировать вне

Последний шаг - анимировать уведомление. Идея заключалась бы в том, чтобы сделать обратную анимацию в.

Animated.parallel([
  Animated.timing(this.state.opacity, {
    toValue: 0,
    duration: 300,
  }),
  Animated.timing(this.state.offset, {
    toValue: height * -1,
    duration: 300,
  }),
])

Мы снова анимируем параллельно, на этот раз мы анимируем наш opacity обратно в 0 и анимируем offset обратно в -height нашего уведомления. Это будет выглядеть так, как будто он одновременно исчезает и уходит за пределы экрана.

Мы хотим, чтобы эти две анимации происходили последовательно, поэтому нам нужно будет использовать команду Animated.sequence, чтобы объединить их. Кроме того, чтобы наш пользователь мог видеть уведомление, мы будем использовать Animated.delay, чтобы подождать, прежде чем перейти к анимации скрытия.

Animated.sequence выполнит каждую анимацию по порядку. Как только анимация будет завершена, она перейдет к следующей анимации.

Итак, в нашем коде ниже.

  1. Мы отображаем наше уведомление, используя Animated.parallel для одновременного выполнения двух анимаций.
  2. Ждем 1.5с
  3. Мы делаем обратную нашу первую анимацию и выполняем две обратные анимации одновременно.
Animated.sequence([

  Animated.parallel([
    Animated.timing(this.state.opacity, {
      toValue: 1,
      duration: 300,
    }),
    Animated.timing(this.state.offset, {
      toValue: 0,
      duration: 300,
    }),
  ]),

  Animated.delay(1500),

  Animated.parallel([
    Animated.timing(this.state.opacity, {
      toValue: 0,
      duration: 300,
    }),
    Animated.timing(this.state.offset, {
      toValue: height * -1,
      duration: 300,
    }),
  ]),

]).start();

Очень важно, чтобы вы вызывали здесь start(), иначе ничего не будет анимировано.

Весь наш handlePress код выглядит так.

handlePress = () => {
    this.setState(
      {
        value: "",
        notification: this.state.value,
      },
      () => {
        this._notification.getNode().measure((x, y, width, height, pageX, pageY) => {
          this.state.offset.setValue(height * -1);

          Animated.sequence([

            Animated.parallel([
              Animated.timing(this.state.opacity, {
                toValue: 1,
                duration: 300,
              }),
              Animated.timing(this.state.offset, {
                toValue: 0,
                duration: 300,
              }),
            ]),

            Animated.delay(1500),

            Animated.parallel([
              Animated.timing(this.state.opacity, {
                toValue: 0,
                duration: 300,
              }),
              Animated.timing(this.state.offset, {
                toValue: height * -1,
                duration: 300,
              }),
            ]),

          ]).start();
        });
      }
    );
  };

Финал

Если вам понравилось, посмотрите больше на Code Daily!

Эти методы могут применяться ко многим различным типам взглядов и ситуаций. Так что исследуйте самостоятельно и посмотрите, что вы можете построить.

Если вы переключили height на использование width, вы могли бы создать систему уведомлений в виде тоста, в которой уведомление скользит слева или справа.

Кроме того, если бы вы изменили положение уведомления на bottom: 0, вы могли бы выдвинуть уведомление снизу.

Полный код здесь Живая демонстрация

Полный код

import React, { Component } from "react";
import {
  AppRegistry,
  Animated,
  Easing,
  StyleSheet,
  Text,
  View,
  TextInput,
  TouchableOpacity,
} from "react-native";

export default class animated_notification extends Component {
  state = {
    value: "",
    notification: "",
    opacity: new Animated.Value(0),
    offset: new Animated.Value(0),
  };
  handlePress = () => {
    this.setState(
      {
        value: "",
        notification: this.state.value,
      },
      () => {
        this._notification.getNode().measure((x, y, width, height, pageX, pageY) => {
          this.state.offset.setValue(height * -1);

          Animated.sequence([

            Animated.parallel([
              Animated.timing(this.state.opacity, {
                toValue: 1,
                duration: 300,
              }),
              Animated.timing(this.state.offset, {
                toValue: 0,
                duration: 300,
              }),
            ]),

            Animated.delay(1500),

            Animated.parallel([
              Animated.timing(this.state.opacity, {
                toValue: 0,
                duration: 300,
              }),
              Animated.timing(this.state.offset, {
                toValue: height * -1,
                duration: 300,
              }),
            ]),

          ]).start();
        });
      }
    );
  };
  render() {
    const notificationStyle = {
      opacity: this.state.opacity,
      transform: [
        {
          translateY: this.state.offset,
        },
      ],
    };

    return (
      <View style={styles.container}>
        <Animated.View style={[styles.notification, notificationStyle]} ref={notification => this._notification = notification}>
            <Text style={styles.notificationText}>
              {this.state.notification}
            </Text>
        </Animated.View>

        <View>
          <TextInput
            style={styles.input}
            value={this.state.value}
            onChangeText={value => this.setState({ value })}
          />
          <TouchableOpacity onPress={this.handlePress}>
            <View style={styles.button}>
              <Text style={styles.buttonText}>Show Notification</Text>
            </View>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  notification: {
    position: "absolute",
    paddingHorizontal: 7,
    paddingVertical: 15,
    left: 0,
    top: 0,
    right: 0,
    backgroundColor: "tomato",
  },
  notificationText: {
    color: "#FFF",
  },
  button: {
    backgroundColor: "tomato",
    padding: 15,
    marginTop: 10,
  },
  buttonText: {
    color: "#FFF",
    textAlign: "center",
  },
  input: {
    width: 250,
    height: 40,
    padding: 5,
    borderWidth: 1,
    borderColor: "#CCC",
  },
});

AppRegistry.registerComponent(
  "animated_notification",
  () => animated_notification
);

Первоначально опубликовано на сайте codedaily.io 22 июня 2017 г.