From 6799dffe42eafcf3a8c5dbcc480e0408c34c19fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 3 Dec 2025 07:18:58 +0100 Subject: [PATCH 01/11] feat: add Reanimated shared value to Rive property binding example Demonstrates driving Rive ViewModelNumberProperty from Reanimated shared values using NitroModules.box() to share HybridObjects with worklets. --- example/assets/rive/movecircle.riv | Bin 0 -> 307 bytes example/package.json | 4 +- .../src/pages/SharedValueListenerExample.tsx | 196 ++++++++++++++++++ example/src/pages/index.ts | 1 + ios/HybridViewModelNumberProperty.swift | 8 +- yarn.lock | 23 +- 6 files changed, 226 insertions(+), 6 deletions(-) create mode 100644 example/assets/rive/movecircle.riv create mode 100644 example/src/pages/SharedValueListenerExample.tsx diff --git a/example/assets/rive/movecircle.riv b/example/assets/rive/movecircle.riv new file mode 100644 index 0000000000000000000000000000000000000000..9e73a5530d564d4f993bd4e63de41c1bdb853ac2 GIT binary patch literal 307 zcmXX>y-LGi6g}6Rm@23s6-T@D0WwH&@TV1rf?ZS!j%&uU!iFe&qj?)MOWPv{IaT`t|hPi;2bYTgJ2@>1)<}W6kfH1KF}0c!1rMyrio!j zf>u12t%euAQ!T?Vu>ySM@Mg5R+Tnt>p(#n=J+~1P)=eu7%;!2;Oxw0S$D>~AQhJm= zWk4BHMwBrnqD(0>3X&!?1Y4QqGxthWTu-HJY-iR^LnlXNuEA8Bgb?n@#s&4OL O_g=qzfPG{iMDPP~b52hH literal 0 HcmV?d00001 diff --git a/example/package.json b/example/package.json index 665a1bfd..3875d363 100644 --- a/example/package.json +++ b/example/package.json @@ -17,7 +17,9 @@ "react-native": "0.79.2", "react-native-gesture-handler": "^2.25.0", "react-native-nitro-modules": "^0.31.3", - "react-native-safe-area-context": "^5.4.0" + "react-native-reanimated": "^4.1.5", + "react-native-safe-area-context": "^5.4.0", + "react-native-worklets-core": "^1.6.2" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/example/src/pages/SharedValueListenerExample.tsx b/example/src/pages/SharedValueListenerExample.tsx new file mode 100644 index 00000000..dd3d4a98 --- /dev/null +++ b/example/src/pages/SharedValueListenerExample.tsx @@ -0,0 +1,196 @@ +import { + View, + Text, + StyleSheet, + Button, + ActivityIndicator, +} from 'react-native'; +import { type Metadata } from '../helpers/metadata'; +import Animated, { + useSharedValue, + useAnimatedReaction, + useAnimatedStyle, + withSpring, +} from 'react-native-reanimated'; +import { useEffect, useMemo } from 'react'; +import { NitroModules } from 'react-native-nitro-modules'; +import { + Fit, + RiveView, + useRiveFile, + type RiveFile, + type ViewModelInstance, + type ViewModelNumberProperty, +} from '@rive-app/react-native'; + +export default function SharedValueListenerExample() { + const { riveFile, isLoading, error } = useRiveFile( + require('../../assets/rive/movecircle.riv') + ); + + return ( + + {isLoading ? ( + + ) : riveFile ? ( + + ) : ( + {error || 'Unexpected error'} + )} + + ); +} + +function WithViewModelSetup({ file }: { file: RiveFile }) { + const viewModel = useMemo(() => file.defaultArtboardViewModel(), [file]); + const instance = useMemo( + () => viewModel?.createDefaultInstance(), + [viewModel] + ); + + if (!instance || !viewModel) { + return ( + + {!viewModel + ? 'No view model found' + : 'Failed to create view model instance'} + + ); + } + + return ; +} + +function AnimatedRiveExample({ + instance, + file, +}: { + instance: ViewModelInstance; + file: RiveFile; +}) { + const progress = useSharedValue(0); + + const boxedProperty = useMemo(() => { + const posYProperty = instance.numberProperty('posY'); + if (!posYProperty) { + return null; + } + return NitroModules.box(posYProperty); + }, [instance]); + + useAnimatedReaction( + () => progress.value, + (value: number) => { + 'worklet'; + if (!boxedProperty) return; + const property = boxedProperty.unbox() as ViewModelNumberProperty; + property.value = value; + }, + [boxedProperty] + ); + + const circleStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: progress.value / 3 }], + })); + + const animateTo800 = () => { + progress.value = 0; + progress.value = withSpring(800, { + damping: 8, + stiffness: 80, + }); + }; + + const animateTo0 = () => { + progress.value = withSpring(0, { + damping: 8, + stiffness: 80, + }); + }; + + useEffect(() => { + animateTo800(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + Circle posY is driven by Reanimated shared value (Red circle is rive, + Blue circle is React Native View) + + + + + + + + +