I'm trying to create shared-element transition. Using ref
i'm accessing the View location (height, width, pageX, pageY) from measure
method and i have another View with absolute position where i'm passing this data so that transition start from that place
but instead after adding the pageX
and pageY
to respective left
and top
- the absolute view is not on the exact location
import { Image } from "expo-image";
import React, { useRef } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated";
const AnimatedExpoImage = Animated.createAnimatedComponent(Image);
const Example = () => {
const imageViewRef = useRef<View>(null);
const rootViewRef = useRef<View>(null);
const position = useSharedValue({ x: 0, y: 0 });
const size = useSharedValue({ width: 0, height: 0 });
const activeImagePositionStyle = useAnimatedStyle(() => {
return {
top: position.value.y,
left: position.value.x,
};
});
const activeImageSizeStyle = useAnimatedStyle(() => {
return {
height: size.value.height,
width: size.value.width,
};
});
return (
<View ref={rootViewRef} style={{ flex: 1, backgroundColor: "white" }}>
<View>
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 50,
}}
>
<Text>Paul Jarvis</Text>
</View>
<View
ref={imageViewRef}
style={{
height: 200,
width: "85%",
alignSelf: "center",
}}
>
<Image
contentFit="cover"
style={{ height: "100%", width: "100%" }}
source={{
uri: "https://picsum.photos/id/16/2500/1667",
}}
/>
</View>
<Button
title="Get Details"
onPress={() => {
imageViewRef.current?.measure(
(x, y, width, height, pageX, pageY) => {
position.value = { x: pageX, y: pageY };
size.value = { width, height };
}
);
}}
/>
</View>
<View style={[StyleSheet.absoluteFill]}>
<View style={{ flex: 1 }}>
<AnimatedExpoImage
contentFit="cover"
style={[
styles.image,
activeImagePositionStyle,
activeImageSizeStyle,
]}
source={{
uri: "https://picsum.photos/id/17/2500/1667",
}}
/>
</View>
</View>
</View>
);
};
export default Example;
const styles = StyleSheet.create({
image: {
width: null,
height: null,
position: "absolute",
top: 0,
left: 0,
},
});
The issue comes from how measure
works. When you call measure
, the values (pageX
, pageY
) are returned in absolute screen coordinates.
But when you position a view with position: "absolute"
, the coordinates need to be relative to the parent container (in your case, rootViewRef
). That’s why simply setting top: pageY
and left: pageX
doesn’t align the view correctly.
The solution is to either convert those coordinates to be relative, or use measureLayout
, which directly gives you coordinates relative to a specific parent:
imageViewRef.current?.measureLayout(
rootViewRef.current,
(x, y, width, height) => {
position.value = { x, y };
size.value = { width, height };
}
);
This way, x
and y
are already relative to the container where your absolutely positioned view lives, so it lines up perfectly.