React Native Animated — разверните круг с центром в прессе

Я пытаюсь создать компонент с помощью Animated on React Native, где круг расширяется от точки нажатия, чтобы охватить весь экран.

Идея состоит в том, что, по сути, это карусель цветов, скажем:

const colors = ['white', 'black', 'green', 'blue', 'red', 'pink'];

Вы начинаете с цвета 0 в качестве фона, а затем, когда вы нажимаете на экран, круг цвета 1 расширяется (из ничего) от точки нажатия и занимает весь экран. В этот момент, когда анимация завершена, colorIndex увеличивается, и анимированный круг одновременно стирается, так что мы плавно перешли к следующему цвету, который теперь является фоном. (Например, сейчас мы используем «черный» в качестве цвета фона).

Затем, когда вы снова нажимаете, процесс повторяется, расширяя круг «зеленого» для заполнения экрана, затем обновляя зеленый цвет, чтобы он стал цветом фона, и т. д.

(Чтобы уточнить, что я ищу в анимации, проверьте этот GIF out (без значков и т. д.)).

Я пробую это с Animated и следующим кодом, вызываемым при нажатии основного фонового компонента (TouchableHighlight):

triggerSwipe(event) {
  this.setState({ location: { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY } });

  Animated.timing(
    this.state.diameter,
    { toValue: Dimensions.get('window').height * 2, duration: 500 },
  ).start(() => {
    this.state.diameter.setValue(0);
    this.setState({ colorIndex: this.state.colorIndex + 1 });
  });
}

Тогда идея заключается в том, что в функции render я устанавливаю left и top моего absolutely компонента круга равными положению, и я устанавливаю width и height равными diameter, а также borderRadius (diameter / 2.0 не будет работать, потому что diameter является Анимированный объект, а не число).

Здесь возникают две проблемы, которые я не могу понять (оба видны в GIF, включенном в нижней части этого вопроса):

  1. Перед каждой анимацией появляется черная вспышка. Он охватывает весь компонент TouchableHighlight. И всегда черный. Какой бы цвет я ни был. В компоненте нет хардкодного черного (!).
  2. Местоположение определяет край круга (или, точнее, квадрат, в который он вписан), а не центр. Я хочу, чтобы он определял центр круга, чтобы круг расширялся наружу от моего пресса.

Я предполагаю, что № 1 действительно трудно рассуждать без всего кода. Поэтому я вставлю полный код компонента ниже. Второй, я надеюсь, немного более концептуален/легко понять, не играя с ним.

Кто-нибудь может придумать хороший способ определить местоположение центра круга? Я беспокоюсь, что это требует постоянного обновления левого/верхнего местоположения круга в зависимости от его диаметра (left: location.x - diameter._value / 2), что, насколько я понимаю, избавит от всех преимуществ собственной производительности Animated. Есть идеи получше?

Вот полный код компонента:

import React from 'react';
import { TouchableHighlight, Animated } from 'react-native';
import { Dimensions } from 'react-native';

const colors = ['white', 'black', 'green', 'blue', 'red', 'pink'];

const color = index => colors[index % colors.length];

class ColorScape extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      colorIndex: 0,
      location: { x: 0, y: 0 },
      diameter: new Animated.Value(0)
    };
  }

  triggerSwipe(event) {
    this.setState({ location: { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY } });

    Animated.timing(
      this.state.diameter,
      { toValue: Dimensions.get('window').height * 2, duration: 500 },
    ).start(() => {
      this.state.diameter.setValue(0);
      this.setState({ colorIndex: this.state.colorIndex + 1 });
    });
  }

  render() {
    const { colorIndex, diameter, location } = this.state;

    const circleStyles = {
      backgroundColor: color(colorIndex + 1),
      width: diameter,
      height: diameter,
      borderRadius: diameter,
      position: 'absolute',
      zIndex: 2,
      left: location.x,
      top: location.y
    };

    return (
      <TouchableHighlight
        style={ {
          flex: 1,
          width: '100%',
          height: '100%',
          zIndex: 1,
          backgroundColor: color(colorIndex)
        } }
        onPress={ (event) => this.triggerSwipe(event) }
      >
        <Animated.View
          style={ circleStyles }
        />
      </TouchableHighlight>
    );
  }
}

export default ColorScape;

Вот текущий функционал (в симуляторе iPhone X):

введите здесь описание изображения


person Sasha    schedule 12.09.2018    source источник


Ответы (1)


Если вы хотите, чтобы ваш круг расширялся от точки щелчка, я бы рекомендовал использовать transform: scale() вместо анимации свойств ширины/высоты. Таким образом, вам нужно анимировать только одно свойство.

Вам все еще нужно центрировать свой круг. Допустим, вы установили ширину/высоту на 100 пикселей. Теперь вы устанавливаете transform: scale(0) в качестве начального размера и можете увеличить свой круг до 1, если хотите, чтобы он занял весь экран. Вы можете поиграть с математикой, чтобы получить правильный выбор времени/ощущения.

При таком подходе у вас ВСЕ ЕЩЕ будет проблема с увеличением круга слева вверху. Это потому, что вы разместили его там. top/left относятся к родительскому контейнеру. Теперь вам нужно центрировать круг относительно самого себя. Это означает, что вам нужно будет добавить еще одно свойство transform. transform: translateX(-50%)translateY(-50%). Это всегда будет центрировать ваш круг относительно его центрального положения.

Начальное преобразование: transform: translateX(-50%)translateY(-50%)scale(0) Увеличенное преобразование: transform: translateX(-50%)translateY(-50%)scale(x), где x означает, насколько большим должен быть круг, который вы хотите получить.

person brianespinosa    schedule 12.09.2018