Всех приветствую! Это вторая и заключительная часть моего цикла статей по созданию кастомного компонента 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 состоянием нашего компонента, путем изменения одноименного пропса:
Пропс 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);
Теперь проверим что у нас получилось:
Обработка изменений состояния value вне компонента
Бывают случаи, когда нам необходимо изменить состояние компонента с помощью другого компонента. В текущей реализации при изменении value ничего не произойдет. Нам необходимо отследить изменение состояния и привести наш круг к нужной точке. Для этого нам понадобится хук useEffect и немного кода:
useEffect(() => {
const currentCircle = !value ? 0 : TRACK_CIRCLE_WIDTH;
if (!!currentCircle !== !!translateX.value) {
translateX.value = withTiming(currentCircle);
}
}, [TRACK_CIRCLE_WIDTH, translateX, value]);
Здесь в константе мы заводим координаты, к которым нам необходимо будет прийти. Далее идет проверка, если текущая позиция относительно концов компонента не равно позиции конца компонента (приводим числа к булевому), то переводим наш круг в нужное положение. Не забываем проставить необходимые зависимости в наш хук.
Проверяем конечный результат:
Заключение
Вот и все, мы разобрались в базовых вещах библиотек Reanimated и Gesture Handler и написали свой собственный кастомный компонент, который пригодится нам в наших проектах. Весь код доступен по ссылке. Надеюсь статья была вам полезной, оставляйте свои комментарии с критикой и пожеланиями, о чем вы бы хотели видеть следующую статью.