Skip to content

Commit 1603a37

Browse files
committed
feat(example): add Rive to Reanimated shared value demo
Demonstrates Rive animation driving React Native UI via data binding listeners.
1 parent 8c2447b commit 1603a37

4 files changed

Lines changed: 221 additions & 0 deletions

File tree

376 Bytes
Binary file not shown.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
2+
import Animated, {
3+
useSharedValue,
4+
useAnimatedStyle,
5+
} from 'react-native-reanimated';
6+
import { useEffect, useMemo } from 'react';
7+
import {
8+
Fit,
9+
RiveView,
10+
useRiveFile,
11+
type RiveFile,
12+
type ViewModelInstance,
13+
} from '@rive-app/react-native';
14+
import { type Metadata } from '../helpers/metadata';
15+
16+
export default function RiveToReactNativeExample() {
17+
const { riveFile, isLoading, error } = useRiveFile(
18+
require('../../assets/rive/bouncing_ball.riv')
19+
);
20+
21+
return (
22+
<View style={styles.container}>
23+
{isLoading ? (
24+
<ActivityIndicator size="large" color="#0000ff" />
25+
) : riveFile ? (
26+
<WithViewModelSetup file={riveFile} />
27+
) : (
28+
<Text style={styles.errorText}>{error || 'Unexpected error'}</Text>
29+
)}
30+
</View>
31+
);
32+
}
33+
34+
function WithViewModelSetup({ file }: { file: RiveFile }) {
35+
const viewModel = useMemo(() => file.defaultArtboardViewModel(), [file]);
36+
const instance = useMemo(
37+
() => viewModel?.createDefaultInstance(),
38+
[viewModel]
39+
);
40+
41+
if (!instance || !viewModel) {
42+
return (
43+
<View style={styles.errorContainer}>
44+
<Text style={styles.errorText}>
45+
{!viewModel
46+
? 'No view model found.'
47+
: 'Failed to create view model instance'}
48+
</Text>
49+
<Text style={styles.instructionText}>
50+
This demo requires a Rive file (bouncing_ball.riv) with:{'\n'}
51+
{'\n'}• A ViewModel with a "ypos" number property{'\n'}• A bouncing
52+
ball animation{'\n'}• Target-to-source binding from ball Y position to
53+
ypos{'\n'}
54+
{'\n'}
55+
See Rive docs for data binding setup.
56+
</Text>
57+
</View>
58+
);
59+
}
60+
61+
return <BouncingBallTracker instance={instance} file={file} />;
62+
}
63+
64+
function BouncingBallTracker({
65+
instance,
66+
file,
67+
}: {
68+
instance: ViewModelInstance;
69+
file: RiveFile;
70+
}) {
71+
const pointerY = useSharedValue(0);
72+
73+
const yposProperty = useMemo(
74+
() => instance.numberProperty('ypos'),
75+
[instance]
76+
);
77+
78+
useEffect(() => {
79+
if (!yposProperty) return;
80+
81+
yposProperty.addListener((value) => {
82+
'worklet';
83+
console.log('worklet:', _WORKLET, __RUNTIME_KIND);
84+
pointerY.value = value;
85+
return true;
86+
});
87+
88+
return () => {
89+
yposProperty.removeListeners();
90+
};
91+
}, [yposProperty, pointerY]);
92+
93+
const pointerStyle = useAnimatedStyle(() => ({
94+
transform: [{ translateY: pointerY.value }],
95+
}));
96+
97+
if (!yposProperty) {
98+
return (
99+
<View style={styles.errorContainer}>
100+
<Text style={styles.errorText}>Property "ypos" not found</Text>
101+
<Text style={styles.instructionText}>
102+
Make sure the Rive file has a "ypos" number property in its ViewModel
103+
with target-to-source binding from the ball&apos;s Y position.
104+
</Text>
105+
</View>
106+
);
107+
}
108+
109+
return (
110+
<View style={styles.container}>
111+
<Text style={styles.subtitle}>
112+
Rive animation drives the ball position.{'\n'}React Native listens and
113+
moves the blue pointer to track it.
114+
</Text>
115+
<Text style={styles.subtitle}>
116+
No re-renders - using direct addListener
117+
</Text>
118+
119+
<View style={styles.contentContainer}>
120+
<RiveView
121+
style={styles.rive}
122+
autoPlay={true}
123+
dataBind={instance}
124+
fit={Fit.Contain}
125+
file={file}
126+
/>
127+
<Animated.View style={[styles.pointer, pointerStyle]}>
128+
<View style={styles.pointerArrow} />
129+
<Text style={styles.pointerText}>RN</Text>
130+
</Animated.View>
131+
</View>
132+
</View>
133+
);
134+
}
135+
136+
RiveToReactNativeExample.metadata = {
137+
name: 'Rive → React Native',
138+
description:
139+
'Demonstrates Rive animation driving React Native UI through data binding listeners',
140+
} satisfies Metadata;
141+
142+
const styles = StyleSheet.create({
143+
container: {
144+
flex: 1,
145+
backgroundColor: '#fff',
146+
},
147+
errorContainer: {
148+
flex: 1,
149+
justifyContent: 'center',
150+
alignItems: 'center',
151+
padding: 20,
152+
},
153+
subtitle: {
154+
fontSize: 14,
155+
color: '#666',
156+
textAlign: 'center',
157+
marginVertical: 10,
158+
paddingHorizontal: 20,
159+
},
160+
valueText: {
161+
fontSize: 18,
162+
fontWeight: 'bold',
163+
textAlign: 'center',
164+
marginBottom: 10,
165+
color: '#333',
166+
},
167+
contentContainer: {
168+
position: 'relative',
169+
height: 600,
170+
width: 200,
171+
alignItems: 'center',
172+
justifyContent: 'center',
173+
borderWidth: 1,
174+
borderColor: '#ccc',
175+
},
176+
rive: {
177+
width: 100,
178+
height: 600,
179+
},
180+
pointer: {
181+
position: 'absolute',
182+
top: -10,
183+
right: 40,
184+
flexDirection: 'row',
185+
alignItems: 'center',
186+
},
187+
pointerArrow: {
188+
width: 0,
189+
height: 0,
190+
borderTopWidth: 10,
191+
borderBottomWidth: 10,
192+
borderRightWidth: 15,
193+
borderTopColor: 'transparent',
194+
borderBottomColor: 'transparent',
195+
borderRightColor: '#007AFF',
196+
},
197+
pointerText: {
198+
backgroundColor: '#007AFF',
199+
color: '#fff',
200+
fontSize: 12,
201+
fontWeight: 'bold',
202+
paddingHorizontal: 6,
203+
paddingVertical: 4,
204+
borderTopRightRadius: 4,
205+
borderBottomRightRadius: 4,
206+
},
207+
errorText: {
208+
color: 'red',
209+
textAlign: 'center',
210+
fontSize: 16,
211+
fontWeight: 'bold',
212+
marginBottom: 10,
213+
},
214+
instructionText: {
215+
color: '#666',
216+
textAlign: 'left',
217+
fontSize: 14,
218+
lineHeight: 22,
219+
},
220+
});

example/src/pages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { default as OutOfBandAssetsWithSuspense } from './OutOfBandAssetsWithSus
99
export { default as ManyViewModels } from './ManyViewModels';
1010
export { default as ResponsiveLayouts } from './ResponsiveLayouts';
1111
export { default as SharedValueListenerExample } from './SharedValueListenerExample';
12+
export { default as RiveToReactNativeExample } from './RiveToReactNativeExample';
376 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)