How can I center a rectangle in a Canvas in React Native and Skia (react-native-skia)?

405 views Asked by At

Summary

I would like to center a Skia Rect inside a Skia Canvas in React Native using react-native-skia (https://github.com/Shopify/react-native-skia). If I refresh my app, or launch it fresh, the Rect is not centered, but if I make a code change and save it (causing a hot-reload), the Rect is centered. I suspect this means I am doing something out-of-order or perhaps not manually performing some kind of refresh such as invalidating the surface, but I'm not sure.

Code

I am using the following code (simplified for the sake of brevity):

const size = useSharedValue({ width: 0, height: 0 });

...
    <Canvas onSize={size} style={{height:200, margin:30, backgroundColor:"purple"}}>
        <Rect x={size.value.width/2 - 50} width={100} height={100} color="lightblue" />
    </Canvas>
...

I am using useSharedValue from react-native-reanimated as recommended in the react-native-skia docs:

https://shopify.github.io/react-native-skia/docs/canvas/overview

Results

The code above results in the center of the rectangle drawn left-justified:

enter image description here

If I make any code change and save my file, the app hot-reloads and the rectangle appears in the correct position:

enter image description here

Why does the rectangle render in the incorrect position initially? I realize that I am setting a value of { width: 0, height: 0 }, but I expected that the value would get overwritten through the onSize event when the initial layout is performed and the Canvas gets its actual size based on explicit properties and flex values.

What Else I've tried

I've also tried to handle the onLayout event to see if I could respond to that change:

const size = useSharedValue({ width: 0, height: 0 });

const handleLayout = (event: LayoutChangeEvent) => {
    const { width, height } = event.nativeEvent.layout;
    console.log(`Width: ${width}, Height: ${height}`);
    size.value = {width:width, height:height};
    };
...
    <Canvas onSize={size} onLayout={handleLayout}  style={{height:200, margin:30, backgroundColor:"purple"}}>
        <Rect x={size.value.width/2 - 50} width={100} height={100} color="lightblue" />
    </Canvas>

The log does produce the correct values, but my Rect is still rendering incorrectly:

 LOG  Width: 312, Height: 200

I also tried adding mode="continuous", as I expected that the Skia canvas was not properly invalidating after the rectangle's x value was assigned, but this also did not solve the problem:

<Canvas onSize={size} onLayout={handleLayout}  style={{height:200, margin:30, backgroundColor:"purple"}} mode="continuous">
    <Rect x={size.value.width/2 - 50} width={100} height={100} color="lightblue" />
</Canvas>
1

There are 1 answers

0
Sliversun On

On your second suggested attempt, use a normal state variable and it should fix your issue.

Something like this

const [size, setSize] = useState<{ width: number, height: number }>({ width: 0, height: 0 })

instead of

const size = useSharedValue({ width: 0, height: 0 });

According to Canvas Documentation For Getting Canvas Size. Use onLayout like a regular react native component. And the onSize if you want to treat it as a skia animation.