Skip to content

[Android] Wrong hit-test/selection when fitFit.None (e.g. Fit.Cover, Fit.FitWidth) #372

@GuetarniWalid

Description

@GuetarniWalid

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions