-
Notifications
You must be signed in to change notification settings - Fork 66
Description
Description
On Android, when I set the fit prop on <Rive /> to anything other than Fit.None (e.g. Fit.Cover, Fit.FitWidth), touch selection becomes desynchronized from what is visually displayed.
In my bottom bar (5 items), tapping visually on item #4 sometimes triggers logic for item #2 (or another wrong item). With Fit.None, selection is correct and stable.
Environment
- rive-react-native: ^9.6.1
- React Native: 0.81.4
- Expo: ^54.0.9 (managed)
- React: 19.1.0
- Android: Android 15
- iOS: not tested
Context
I have a bottom navigation with 5 items. In my Rive file, each item has a pointerDown listener configured directly in Rive Editor. When tapping an item (e.g. item #4), the listener updates a bound variable inside the State Machine, and this variable drives translation constraints that move the square “indicator” shape under the selected item.
This means the selection logic (which item is active, which constraint = 100% vs 0%) is fully defined inside the Rive file, not in my React Native code. From the app side, I only start the State Machine with autoplay and let Rive handle item selection.
When fit is set to Fit.none, everything works fine: tapping item #4 triggers the listener for item #4 and moves the indicator correctly. But with fit={Fit.Layout} (or Fit.Cover, Fit.FitWidth), tapping item #4 may trigger the wrong listener (e.g. item #2), which makes the navigation inconsistent.
Expected behavior
Tapping the visually targeted item should select that exact item, regardless of fit.
Actual behavior
With fit={Fit.Cover} (or Fit.FitWidth), tapping item #4 frequently selects item #2 (or another wrong index).
With fit={Fit.None}, selection matches visuals.
Minimal repro code
import { FC, useRef } from 'react';
import { StyleSheet, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Rive, { AutoBind, Fit, RiveRef } from 'rive-react-native';
const stateMachineName = 'State Machine 1';
const resourceName = 'bottom_navigation';
const artboardName = 'Artboard';
const NAV_HEIGHT = 90;
export const BottomNavigation: FC = () => {
const riveRef = useRef<RiveRef>(null);
return (
<SafeAreaView edges={['bottom']}>
<View style={styles.navWrapper}>
<Rive
ref={riveRef}
resourceName={resourceName}
artboardName={artboardName}
stateMachineName={stateMachineName}
autoplay={true}
dataBinding={AutoBind(true)}
fit={Fit.Layout}
onPlay={() => {
const rive = riveRef.current;
if (!rive) return;
console.log('Play - rive ');
}}
onError={(error) => {
console.log('Error loading Rive animation', error);
}}
onStateChanged={(_, stateName: string) => {
console.log('State changed - stateName : ', stateName);
}}
/>
</View>
<View style={styles.bottomBackground} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
navWrapper: { height: NAV_HEIGHT },
bottomBackground: { backgroundColor: '#00000000' },
});Rive file / setup
- 5 bottom-bar items.
- A square indicator moves under the selected item. The active item is determined by a SelectedItem variable inside the State Machine (a number from 1 to 5).
- In Rive, timelines use the value of this variable to apply the translation constraints: the selected item’s constraint is set to 100%, and all others to 0%.
- Tap listeners are configured as pointerDown on each shape (also tested on layout: same result).
Workarounds
fit={Fit.None}→ selection is correct.- Keeping the native view bounds equal to the artboard size (avoiding scaling) also avoids the mismatch, but removes responsive layout.
Hypothesis
Touch coordinates aren’t transformed with the same matrix as the drawing transform on Android when fit applies scaling/letterboxing.
This leads to hit-test/selection using unscaled coordinates while visuals are scaled, causing index mismatches.
Artifacts
Notes
- iOS: not tested.
- The mismatch reproduces whether the tap listener is on the shape (
pointerDown) or on the layout. - When the issue occurs, the onStateChanged logs confirm the wrong item is reported.
For example, tapping visually on item 4 may activate item 2, and the logs correctly show "SelectedItem = 2".
This indicates the error originates from the hit-test/coordinate mapping, not from the animation or indicator constraints themselves.