@Dmitry Vasilevsky
После обновления react navigation до v6, при использовании native stack пропадает возможность использовать https://github.com/IjzerenHein/react-navigation-shared-element. При этом сейчас данный пакет уже не имеет поддержки.
Все же эффект shared element transition не так сложно с анимировать вручную. Для этого необходимо будет сделать всего несколько шагов.
Предположим нужно сделать подобный эффект для картинки из списка на первом экране, анимируя всё это на второй экран.
Первоначально нужно что-то сделать с дефолтной анимацией второго экрана. Можно поставить анимацию непрозрачности, тогда не придется создавать её самим.
<StackTabOne.Screen
name="FoxScreen"
component={FoxScreen}
options={{ animation: "fade" }}
/>
Если убрать анимацию вовсе, нужно также добавить непрозрачность для фона экранов и придумать свою анимацию. Так делает оригинальный пакет. Помимо этого он перехватывает анимацию возвращения и добавляет обратный эффект. Этого мы делать не будем.
Теперь при нажатии на элемент списка нужно передать информацию о размерах и положении картинки, для этого мы обернём её в дополнительный View и повесим ref.
// Item.jsx
<View ref={ref} collapsable={false}>
<Image
source={{ uri: item.image }}
style={styles.image}
/>
</View>
При нажатии на элемент будем вычислять нужное с помощью функции measure
****и передавать вычисления на следующий экран.
// Item.jsx
const ref = React.useRef<View>(null);
const pressHandler = () => {
ref.current?.measure((...measurements: number[]) => {
navigation.navigate("FoxScreen", { item, measurements });
});
};
Напишем компонент-обертку <SharedView></SharedView>
, который будет принимать вычисления компонента на первом экране и анимировать всё в начальное положение компонента на втором экране.
Хочется сразу отметить, что onLayout
высчитывает относительные координаты, а measure
глобальные, поэтому данный компонент не универсален. Это просто пример, ведь для разных ситуаций нужно анимировать по разному.
import * as React from "react";
import { Animated, ViewProps } from "react-native";
interface LayoutValues {
width: number;
height: number;
x: number;
y: number;
}
interface SharedViewProps extends ViewProps {
measurements: number[];
}
const SharedView = (props: SharedViewProps) => {
const { measurements, style, ...otherProps } = props;
const [layout, setLayout] = React.useState<LayoutValues>();
const animation = React.useRef(new Animated.Value(0)).current;
const animatedStyles = () => {
if (!layout) return { opacity: 0 };
const [, , width, height, x, y] = measurements;
const scaleX = width / layout.width;
const scaleY = height / layout.height;
const translateX = x + width / 2 - (layout.x + layout.width / 2);
const translateY = y + height / 2 - (layout.y + layout.height / 2);
return {
transform: [
{
translateX: animation.interpolate({
inputRange: [0, 1],
outputRange: [translateX, 0],
}),
},
{
translateY: animation.interpolate({
inputRange: [0, 1],
outputRange: [translateY, 0],
}),
},
{
scaleX: animation.interpolate({
inputRange: [0, 1],
outputRange: [scaleX, 1],
}),
},
{
scaleY: animation.interpolate({
inputRange: [0, 1],
outputRange: [scaleY, 1],
}),
},
],
};
};
React.useEffect(() => {
Animated.timing(animation, {
toValue: 1,
duration: 300,
useNativeDriver: false,
}).start();
}, [layout]);
return (
<Animated.View
style={[style, animatedStyles()]}
onLayout={(event) => setLayout(event.nativeEvent.layout)}
{...otherProps}
/>
);
};
export default SharedView;
Обернем картинку на втором экране.
<SharedView measurements={measurements}>
<Image
source={{ uri: item.image }}
style={styles.image}
/>
</SharedView>