1- import { View , Text , StyleSheet , ActivityIndicator } from 'react-native' ;
1+ import {
2+ View ,
3+ Text ,
4+ StyleSheet ,
5+ Button ,
6+ ActivityIndicator ,
7+ } from 'react-native' ;
28import { type Metadata } from '../helpers/metadata' ;
39import Animated , {
410 useSharedValue ,
511 useAnimatedReaction ,
612 useAnimatedStyle ,
713 withSpring ,
8- withTiming ,
914} from 'react-native-reanimated' ;
1015import {
1116 Gesture ,
1217 GestureDetector ,
1318 GestureHandlerRootView ,
1419} from 'react-native-gesture-handler' ;
15- import { useMemo } from 'react' ;
16- import { NitroModules } from 'react-native-nitro-modules' ;
20+ import { useCallback , useEffect , useMemo } from 'react' ;
21+ import { NitroModules , type BoxedHybridObject } from 'react-native-nitro-modules' ;
1722import {
1823 Fit ,
1924 RiveView ,
2025 useRiveFile ,
2126 type RiveFile ,
27+ type RiveViewRef ,
2228 type ViewModelInstance ,
2329 type ViewModelNumberProperty ,
2430} from '@rive-app/react-native' ;
@@ -68,8 +74,9 @@ function AnimatedRiveExample({
6874 instance : ViewModelInstance ;
6975 file : RiveFile ;
7076} ) {
71- const pressed = useSharedValue ( false ) ;
72- const offset = useSharedValue ( 0 ) ;
77+ const progress = useSharedValue ( 0 ) ;
78+ const startY = useSharedValue ( 0 ) ;
79+ const viewRef = useSharedValue < BoxedHybridObject < RiveViewRef > | null > ( null ) ;
7380
7481 const boxedProperty = useMemo ( ( ) => {
7582 const posYProperty = instance . numberProperty ( 'posY' ) ;
@@ -79,39 +86,66 @@ function AnimatedRiveExample({
7986 return NitroModules . box ( posYProperty ) ;
8087 } , [ instance ] ) ;
8188
82- const pan = Gesture . Pan ( )
83- . onBegin ( ( ) => {
84- pressed . value = true ;
85- } )
86- . onChange ( ( event ) => {
87- offset . value = event . translationY * 3 ;
88- } )
89- . onFinalize ( ( ) => {
90- offset . value = withSpring ( 0 ) ;
91- pressed . value = false ;
92- } ) ;
93-
9489 useAnimatedReaction (
95- ( ) => offset . value ,
90+ ( ) => progress . value ,
9691 ( value : number ) => {
9792 'worklet' ;
9893 if ( ! boxedProperty ) return ;
99- const property = boxedProperty . unbox ( ) as ViewModelNumberProperty ;
94+ const property = boxedProperty . unbox ( ) ;
10095 property . value = value ;
96+
97+ viewRef . value ?. unbox ( ) ?. _playIfNeeded ( ) ;
10198 } ,
10299 [ boxedProperty ]
103100 ) ;
104101
102+ const panGesture = Gesture . Pan ( )
103+ . onStart ( ( ) => {
104+ 'worklet' ;
105+ startY . value = progress . value ;
106+ } )
107+ . onUpdate ( ( event ) => {
108+ 'worklet' ;
109+ progress . value = startY . value + event . translationY * 3 ;
110+ } )
111+ . onEnd ( ( event ) => {
112+ 'worklet' ;
113+ // Use velocity from gesture to set initial velocity of spring
114+ progress . value = withSpring ( progress . value > 400 ? 800 : 0 , {
115+ damping : 10 ,
116+ stiffness : 100 ,
117+ velocity : event . velocityY * 3 ,
118+ } ) ;
119+ } ) ;
120+
105121 const circleStyle = useAnimatedStyle ( ( ) => ( {
106- transform : [ { translateY : offset . value / 3 } ] ,
107- backgroundColor : pressed . value ? '#FFE04B' : 'blue' ,
108- scale : withTiming ( pressed . value ? 1.2 : 1 ) ,
122+ transform : [ { translateY : progress . value / 3 } ] ,
109123 } ) ) ;
110124
125+ const animateTo800 = useCallback ( ( ) => {
126+ progress . value = 0 ;
127+ progress . value = withSpring ( 800 , {
128+ damping : 8 ,
129+ stiffness : 80 ,
130+ } ) ;
131+ } , [ progress ] ) ;
132+
133+ const animateTo0 = ( ) => {
134+ progress . value = withSpring ( 0 , {
135+ damping : 8 ,
136+ stiffness : 80 ,
137+ } ) ;
138+ } ;
139+
140+ useEffect ( ( ) => {
141+ animateTo800 ( ) ;
142+ } , [ animateTo800 ] ) ;
143+
111144 return (
112145 < GestureHandlerRootView style = { styles . container } >
113146 < Text style = { styles . subtitle } >
114- Drag the blue circle to move both circles. Release to spring back.
147+ Drag the blue circle to control position. Release to spring with
148+ velocity. (Red = Rive, Blue = React Native)
115149 </ Text >
116150
117151 < View style = { styles . riveContainer } >
@@ -122,11 +156,21 @@ function AnimatedRiveExample({
122156 fit = { Fit . Layout }
123157 layoutScaleFactor = { 1 }
124158 file = { file }
159+ hybridRef = { {
160+ f : ( ref ) => {
161+ viewRef . value = NitroModules . box ( ref ) ;
162+ } ,
163+ } }
125164 />
126- < GestureDetector gesture = { pan } >
165+ < GestureDetector gesture = { panGesture } >
127166 < Animated . View style = { [ styles . blueCircle , circleStyle ] } />
128167 </ GestureDetector >
129168 </ View >
169+
170+ < View style = { styles . buttonContainer } >
171+ < Button title = "Bounce to 800" onPress = { animateTo800 } />
172+ < Button title = "Bounce to 0" onPress = { animateTo0 } />
173+ </ View >
130174 </ GestureHandlerRootView >
131175 ) ;
132176}
@@ -141,11 +185,18 @@ const styles = StyleSheet.create({
141185 flex : 1 ,
142186 backgroundColor : '#fff' ,
143187 } ,
188+ title : {
189+ fontSize : 24 ,
190+ fontWeight : 'bold' ,
191+ textAlign : 'center' ,
192+ marginTop : 20 ,
193+ marginBottom : 10 ,
194+ } ,
144195 subtitle : {
145196 fontSize : 16 ,
146197 color : '#666' ,
147198 textAlign : 'center' ,
148- marginVertical : 20 ,
199+ marginBottom : 20 ,
149200 paddingHorizontal : 20 ,
150201 } ,
151202 riveContainer : {
@@ -162,6 +213,12 @@ const styles = StyleSheet.create({
162213 textAlign : 'center' ,
163214 padding : 20 ,
164215 } ,
216+ buttonContainer : {
217+ flexDirection : 'row' ,
218+ justifyContent : 'center' ,
219+ gap : 20 ,
220+ padding : 20 ,
221+ } ,
165222 blueCircle : {
166223 position : 'absolute' ,
167224 left : 50 ,
0 commit comments