Создание кастомного React Native компонента Switch с помощью библиотек Reanimated и Gesture Handler (Часть 2)

Всех приветствую! Это вторая и заключительная часть моего цикла статей по созданию кастомного компонента Switch с помощью библиотек Reanimated и Gesture Handler. Здесь мы рассмотрим реализацию логики пропса disabled, добавим пару новых фич и напишем обработку изменения состояния value вне компонента. Ознакомиться с первой частью можно здесь.

Пропс Disabled

Иногда бывают ситуации, когда нам необходимо дизейблить любые взаимодействия с компонентом, например чтобы не плодить кучу запросов на сервер и ждать выполнение асинхронной функции. В этом нам поможет специальный метод enabled, который мы добавим к нашим константам pan и tap:

 const tap = Gesture.Tap()
   .onEnd(() => {
     translateX.value = withTiming(value ? 0 : TRACK_CIRCLE_WIDTH);
     runOnJS(onValueChange)(!value);
   })
   .enabled(!disabled);
 const pan = Gesture.Pan()
   .onUpdate(({ translationX }) => {
     const translate = value
       ? TRACK_CIRCLE_WIDTH + translationX
       : translationX;
     const currentTranslate = () => {
       if (translate < 0) {
         return 0;
       }
       if (translate > TRACK_CIRCLE_WIDTH) {
         return TRACK_CIRCLE_WIDTH;
       }
       return translate;
     };
     translateX.value = currentTranslate();
   })
   .onEnd(({ translationX }) => {
     const translate = value
       ? TRACK_CIRCLE_WIDTH + translationX
       : translationX;
     const selectedSnapPoint =
       translate > TRACK_CIRCLE_WIDTH / 2 ? TRACK_CIRCLE_WIDTH : 0;
     translateX.value = withTiming(selectedSnapPoint, { duration: 100 });
     runOnJS(onValueChange)(!!selectedSnapPoint);
   })
   .enabled(!disabled);

Добавляем наш пропс disabled в тип принимаемых пропсов компонента:

type SwitchProps = {
  value: boolean;
  onValueChange: (value: boolean) => void;
  disabled?: boolean;
};

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

  • ​​activeColor — цвет компонента в состоянии value = true и disabled = false

  • inactiveColor — цвет компонента в состоянии value = false и disabled = false

  • disabledActiveColor — цвет компонента в состоянии value = true и disabled = true

  • disabledInactiveColor — цвет компонента в состоянии value = true и disabled = true

type SwitchProps = {
  value: boolean;
  onValueChange: (value: boolean) => void;
  disabled?: boolean;
  activeColor?: string;
  inactiveColor?: string;
  disabledActiveColor?: string;
  disabledInactiveColor?: string;
};

По дефолту выставим следующие цвета:

activeColor = 'darkblue',

 inactiveColor = 'darkgray',

 disabledActiveColor = 'blue',

 disabledInactiveColor = 'gray',

Немного переделаем стили нашего контейнера:

  const animatedContainerStyle = useAnimatedStyle(() => {
    const colors = disabled
      ? [disabledInactiveColor, disabledActiveColor]
      : [inactiveColor, activeColor];
    return {
      backgroundColor: interpolateColor(
        translateX.value,
        [0, TRACK_CIRCLE_WIDTH],
        colors
      ),
    };
  });

Здесь в константе colors мы выбираем один из двух массивов нужных нам цветов, необходимых нам для интерполяции, в зависимости от состояния пропса disabled

Теперь мы можем управлять disabled состоянием нашего компонента, путем изменения одноименного пропса:

b76e381532f6f6eab06cc2cf6008aabd.gif

Пропс shouldCancelWhenOutside

У библиотеки Gesture Handler имеется один интересный метод shouldCancelWhenOutside, который отвечает за прекращение отслеживания свайпа вне границ компонента. Добавим этот метод и одноименный пропс также, как и предыдущий, но только для константы pan:

type SwitchProps = {
  value: boolean;
  onValueChange: (value: boolean) => void;
  disabled?: boolean;
  activeColor?: string;
  inactiveColor?: string;
  disabledActiveColor?: string;
  disabledInactiveColor?: string;
  shouldCancelWhenOutside?: boolean;
};
const pan = Gesture.Pan()
   .onUpdate(({ translationX }) => {
     const translate = value
       ? TRACK_CIRCLE_WIDTH + translationX
       : translationX;
     const currentTranslate = () => {
       if (translate < 0) {
         return 0;
       }
       if (translate > TRACK_CIRCLE_WIDTH) {
         return TRACK_CIRCLE_WIDTH;
       }
       return translate;
     };
     translateX.value = currentTranslate();
   })
   .onEnd(({ translationX }) => {
     const translate = value
       ? TRACK_CIRCLE_WIDTH + translationX
       : translationX;
     const selectedSnapPoint =
       translate > TRACK_CIRCLE_WIDTH / 2 ? TRACK_CIRCLE_WIDTH : 0;
     translateX.value = withTiming(selectedSnapPoint, { duration: 100 });
     runOnJS(onValueChange)(!!selectedSnapPoint);
   })
   .enabled(!disabled)
   .shouldCancelWhenOutside(shouldCancelWhenOutside);

Теперь проверим что у нас получилось:

d0b327c9715e66718029222140138ba0.gif

Обработка изменений состояния value вне компонента

Бывают случаи, когда нам необходимо изменить состояние компонента с помощью другого компонента. В текущей реализации при изменении value ничего не произойдет. Нам необходимо отследить изменение состояния и привести наш круг к нужной точке. Для этого нам понадобится хук useEffect и немного кода:

useEffect(() => {
   const currentCircle = !value ? 0 : TRACK_CIRCLE_WIDTH;
   if (!!currentCircle !== !!translateX.value) {
     translateX.value = withTiming(currentCircle);
   }
 }, [TRACK_CIRCLE_WIDTH, translateX, value]);

Здесь в константе мы заводим координаты, к которым нам необходимо будет прийти. Далее идет проверка, если текущая позиция относительно концов компонента не равно позиции конца компонента (приводим числа к булевому), то переводим наш круг в нужное положение. Не забываем проставить необходимые зависимости в наш хук.

Проверяем конечный результат:

a34c94cf59dac01a06e63cc312153e6b.gif

Заключение

Вот и все, мы разобрались в базовых вещах библиотек Reanimated и Gesture Handler и написали свой собственный кастомный компонент, который пригодится нам в наших проектах. Весь код доступен по ссылке. Надеюсь статья была вам полезной, оставляйте свои комментарии с критикой и пожеланиями, о чем вы бы хотели видеть следующую статью.

© Habrahabr.ru