Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions android/src/new/java/com/rive/RiveReactNativeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,7 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
this@RiveReactNativeView.surfaceWidth = w
this@RiveReactNativeView.surfaceHeight = h
this@RiveReactNativeView.needsRedraw = true
// Since 11.7.x the render target keeps its creation-time size and
// RiveSurface.resize() is internal to the SDK, so only the artboard
// is resized here (same behavior as before the 11.7.2 bump).
this@RiveReactNativeView.riveSurface?.let { RiveSurfaceResize.resize(it, w, h) }
resizeArtboardIfLayout()
}

Expand Down Expand Up @@ -331,6 +329,10 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
val rs = riveSurface ?: return
val art = artboard ?: return
art.resizeArtboard(rs, fit.scaleFactor)
// The resize applies on the next advance, which the render loop skips once settled or paused.
val worker = riveWorker ?: return
val sm = stateMachineHandle ?: return
worker.advanceStateMachine(sm, Duration.ZERO)
}
}

Expand Down
40 changes: 40 additions & 0 deletions android/src/new/java/com/rive/RiveSurfaceResize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.rive

import android.util.Log
import app.rive.core.RiveSurface
import java.lang.reflect.InvocationTargetException

/**
* Upstream `RiveView` resizes its surface on every size change through the
* Kotlin-internal `RiveSurface.resize`, so reflection reaches it under its
* mangled JVM name (rive-app/rive-nitro-react-native#418).
*/
internal object RiveSurfaceResize {
private const val TAG = "RiveSurfaceResize"

private val resizeMethod by lazy {
val signature = arrayOf(Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
val method = RiveSurface::class.java.declaredMethods.firstOrNull {
(it.name == "resize" || it.name.startsWith("resize$")) &&
it.parameterTypes.contentEquals(signature)
}
if (method == null) Log.w(TAG, "RiveSurface.resize not found")
method
}

/** Returns false when the surface keeps its previous size. */
fun resize(surface: RiveSurface, width: Int, height: Int): Boolean {
if (width <= 0 || height <= 0) return false
val method = resizeMethod ?: return false
return try {
method.invoke(surface, width, height)
true
} catch (e: InvocationTargetException) {
Log.w(TAG, "RiveSurface.resize(${width}x$height) failed", e.targetException)
false
} catch (e: ReflectiveOperationException) {
Log.w(TAG, "RiveSurface.resize(${width}x$height) failed", e)
false
}
}
}
90 changes: 90 additions & 0 deletions example/src/reproducers/Issue418SurfaceResize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useCallback, useRef, useState } from 'react';
import { Animated, Pressable, Text, View, StyleSheet } from 'react-native';
import { Fit, RiveView, useRiveFile } from '@rive-app/react-native';
import { type Metadata } from '../shared/metadata';

/**
* Issue #418: on the Android new backend, resizing a mounted RiveView kept
* rendering into a surface sized at creation time, so Fit.Layout content
* looked scaled and offset until remount.
*
* Toggle the height, then Remount at the same height: both should look
* identical.
*/

const SHORT = 200;
const TALL = 400;

export default function Issue418SurfaceResize() {
const { riveFile, error } = useRiveFile(
require('../../assets/rive/layout_test.riv')
);
const [height] = useState(() => new Animated.Value(SHORT));
const tall = useRef(false);
const [isTall, setIsTall] = useState(false);
const [generation, setGeneration] = useState(0);

const toggle = useCallback(() => {
tall.current = !tall.current;
setIsTall(tall.current);
Animated.timing(height, {
toValue: tall.current ? TALL : SHORT,
duration: 400,
useNativeDriver: false,
}).start();
}, [height]);

return (
<View style={styles.container}>
<Text style={styles.subtitle}>
target {isTall ? TALL : SHORT}pt · generation {generation}
</Text>
{error ? <Text style={styles.error}>{String(error)}</Text> : null}
<View style={styles.buttons}>
<Pressable style={styles.button} onPress={toggle}>
<Text style={styles.buttonText}>Toggle height</Text>
</Pressable>
<Pressable
style={styles.button}
onPress={() => setGeneration((g) => g + 1)}
>
<Text style={styles.buttonText}>Remount</Text>
</Pressable>
</View>
<Animated.View style={[styles.box, { height }]}>
{riveFile ? (
<RiveView
key={generation}
file={riveFile}
fit={Fit.Layout}
autoPlay
style={styles.rive}
/>
) : null}
</Animated.View>
</View>
);
}

Issue418SurfaceResize.metadata = {
name: 'Issue #418: Surface resize',
description:
'Android Fit.Layout content stale after the view resizes until remount',
} satisfies Metadata;

const styles = StyleSheet.create({
container: { flex: 1, padding: 16, backgroundColor: '#fff' },
subtitle: { fontSize: 14, color: '#666' },
error: { color: 'red', marginTop: 8 },
buttons: { flexDirection: 'row', gap: 8 },
box: { marginTop: 16, backgroundColor: '#1a2a3a' },
rive: { flex: 1 },
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 8,
marginTop: 8,
},
buttonText: { color: '#fff', fontWeight: '600' },
});
Loading