Skip to content

Fit.LAYOUT artboard size incorrect when onMeasure is deferred or state machine animates artboard width #446

Description

@mfazekas

Description

Fit.LAYOUT artboard size is incorrect under two conditions:

  1. layoutScaleFactorAutomatic defaults to 1.0 and is only set to device density in onMeasure(). Since the view hasn't been measured yet when setRiveFile() is called, resizeArtboard() computes pixelWidth / 1.0 instead of pixelWidth / density — the artboard ends up density× too wide. iOS avoids this by initializing the scale factor at view init time. Originally reported in rive-nitro-react-native#206, simple workaround possible by setting layoutScaleFactor to density before setRiveFile.

  2. When a state machine animates the artboard width: keyframes overwrite m_Width every frame via CoreRegistry::setDouble(artboard, widthPropertyKey, ...) during advance(). Since resizeArtboard() only runs once (one-shot requireArtboardResize flag), the artboard stays at the keyframe value.

For reference, the iOS runtime approaches this differently: it initializes scale factor at view init time and re-applies artboard dimensions every frame in drawRive rather than using a one-shot flag. It also re-applies on size change in drawableSizeDidChange. Not saying this is necessarily the right approach for Android, but it avoids both issues above.

Versions, Device, and Other Information

  • Rive Android Runtime Version: 11.3.2
  • Rive API: Legacy (View-based)
  • Device: Android API 35 emulator (1080x2220, 440dpi), also reported on OnePlus CPH2719, Samsung Galaxy Fold 3
  • App Minimum SDK Level: 21
  • App Target SDK Level: 36
  • Frequency: Issue 1 — 100% when setRiveFile is called before onMeasure. Issue 2 — 9/10 with layout_test.riv.

Reproduction Steps

Issue 1 (scale factor default): Any .riv with Fit.LAYOUT. The view hasn't been measured when setRiveFile is called, so layoutScaleFactorAutomatic is still 1.0. Confirmed via logcat:

configure: reload=true fit=LAYOUT viewSize=0x0px
configure post-setRiveFile: artboardW=1920.0 artboardH=1080.0
onLayout: artboardW=1920.0 artboardH=1080.0  ← still intrinsic size after layout

Reproduced in bare Android by suppressing onMeasure:

✗ Artboard 1080 dp ≠ View 393 dp (2.8x too wide!)

The 2.75x factor matches exactly the device density — 1080px / 1.0 instead of 1080px / 2.75.

Issue 2 (keyframe overwrite): layout_test.riv with Fit.LAYOUT — the state machine animates the artboard width property, overwriting the value set by resizeArtboard() every frame. Reproduced 9/10 launches.

Source .riv/.rev File

  • Issue 1: Any .riv with Fit.LAYOUT (tested with GradientBorder.riv from user's repro)
  • Issue 2: layout_test.riv from rive-android example app

Expected Behavior

Artboard dimensions should match viewWidth / density for Fit.LAYOUT.

Screenshots

Additional context

For reference, iOS handles this in:

Activity

  1. added a commit that references this issue on Apr 10, 2026
    6c4d1ef
  2. changed the title [-]Fit.LAYOUT artboard not resizing to match view dimensions[/-] [+]Fit.LAYOUT artboard size incorrect when onMeasure is deferred or state machine animates artboard width[/+] on Apr 10, 2026
  3. added 4 commits that reference this issue on Apr 13, 2026
    1d19a62
    342c6da
    a2a09f2
    8b4219d
  4. sebastian-gallese-speak commented on Apr 22, 2026

    @sebastian-gallese-speak

    We're hitting what appears to be the same architectural issue with Fit.Contain on rive-android 11.4.1. When the Rive Compose composable's view size changes while the state machine is advancing frames, the artboard renders at a stale transform — visible content draws anchored to a screen corner regardless of where Compose places the view.

    Verified via onGloballyPositioned that the view is laid out correctly at the new size. Three variants that avoid the bug, consistent with the advance() ↔ resize race:

    • Pausing the state machine before resize → renders correctly.
    • Letting the state machine reach a non-looping animation's last keyframe first, so advance() is effectively a no-op → resize works.
    • Never resizing the native view (use Modifier.graphicsLayer { scaleX = s; scaleY = s } on a fixed-size Rive composable instead) → always works. Worth noting as a general defense for other teams who may hit different-Fit-mode variants of this.

    For Fit.Contain, the stale state is the draw transform (scale + translate) rather than the artboard width — different code path from the one #447/#450 targets, but same family.

    #450 currently scopes the per-frame recompute to Fit.LAYOUT; broadening to also recompute under Fit.Contain (and other non-LAYOUT Fits) would cover this case too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions