From 58fa14170863c1e5f1ed63ecaead13d81197be2b Mon Sep 17 00:00:00 2001 From: Melissa Vallee Date: Thu, 11 Jun 2026 16:02:57 -0400 Subject: [PATCH 1/2] fix: preserve Android EaseView visuals on drop and add example --- android/src/main/java/com/ease/EaseView.kt | 13 +- .../src/main/java/com/ease/EaseViewManager.kt | 8 ++ example/app/issues/45/_layout.tsx | 29 ++++ example/app/issues/45/index.tsx | 70 ++++++++++ example/app/issues/45/modal.tsx | 128 ++++++++++++++++++ example/src/demos/index.ts | 5 + 6 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 example/app/issues/45/_layout.tsx create mode 100644 example/app/issues/45/index.tsx create mode 100644 example/app/issues/45/modal.tsx diff --git a/android/src/main/java/com/ease/EaseView.kt b/android/src/main/java/com/ease/EaseView.kt index be32c29..7815088 100644 --- a/android/src/main/java/com/ease/EaseView.kt +++ b/android/src/main/java/com/ease/EaseView.kt @@ -691,7 +691,7 @@ class EaseView(context: Context) : ReactViewGroup(context) { private fun applyBackgroundColor(color: Int) { currentBackgroundColor = color - setBackgroundColor(color) + BackgroundStyleApplicator.setBackgroundColor(this, color) } private fun animateBackgroundColor(fromColor: Int, toColor: Int, config: TransitionConfig, loop: Boolean = false) { @@ -714,8 +714,7 @@ class EaseView(context: Context) : ReactViewGroup(context) { } addUpdateListener { animation -> val color = animation.animatedValue as Int - this@EaseView.currentBackgroundColor = color - this@EaseView.setBackgroundColor(color) + this@EaseView.applyBackgroundColor(color) } addListener(object : AnimatorListenerAdapter() { private var cancelled = false @@ -983,7 +982,7 @@ class EaseView(context: Context) : ReactViewGroup(context) { runningSpringAnimations.remove(viewProperty) } - fun cleanup() { + fun stopAnimations() { for (runnable in pendingDelayedRunnables) { removeCallbacks(runnable) } @@ -1004,6 +1003,12 @@ class EaseView(context: Context) : ReactViewGroup(context) { setLayerType(savedLayerType, null) } activeAnimationCount = 0 + pendingBatchAnimationCount = 0 + anyInterrupted = false + } + + fun cleanup() { + stopAnimations() prevOpacity = null prevTranslateX = null diff --git a/android/src/main/java/com/ease/EaseViewManager.kt b/android/src/main/java/com/ease/EaseViewManager.kt index 7d51d74..725d924 100644 --- a/android/src/main/java/com/ease/EaseViewManager.kt +++ b/android/src/main/java/com/ease/EaseViewManager.kt @@ -225,7 +225,15 @@ class EaseViewManager : ReactViewManager() { override fun onDropViewInstance(view: ReactViewGroup) { super.onDropViewInstance(view) + (view as? EaseView)?.stopAnimations() + } + + override fun prepareToRecycleView( + reactContext: ThemedReactContext, + view: ReactViewGroup + ): ReactViewGroup? { (view as? EaseView)?.cleanup() + return super.prepareToRecycleView(reactContext, view) } override fun getExportedCustomDirectEventTypeConstants(): Map? { diff --git a/example/app/issues/45/_layout.tsx b/example/app/issues/45/_layout.tsx new file mode 100644 index 0000000..136a8e0 --- /dev/null +++ b/example/app/issues/45/_layout.tsx @@ -0,0 +1,29 @@ +import { Stack } from 'expo-router'; + +export default function Issue45Layout() { + return ( + <> + + + + + + + ); +} diff --git a/example/app/issues/45/index.tsx b/example/app/issues/45/index.tsx new file mode 100644 index 0000000..5a04e1d --- /dev/null +++ b/example/app/issues/45/index.tsx @@ -0,0 +1,70 @@ +import { useRouter } from 'expo-router'; +import { Pressable, StyleSheet, Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +export default function Issue45Home() { + const insets = useSafeAreaInsets(); + const router = useRouter(); + + return ( + + Issue #45 Android modal background + + Open the modal, then dismiss it with the back gesture or header back + button. The red animated card and green style card should keep their + backgrounds throughout the screen transition. + + + https://github.com/AppAndFlow/react-native-ease/issues + + + [styles.button, pressed && styles.buttonDown]} + onPress={() => router.push('/issues/45/modal')} + > + Open Modal + + + ); +} + +const styles = StyleSheet.create({ + root: { + flex: 1, + backgroundColor: '#1a1a2e', + paddingHorizontal: 20, + }, + heading: { + color: '#fff', + fontSize: 22, + fontWeight: '700', + marginBottom: 8, + }, + body: { + color: '#aaaacc', + fontSize: 14, + lineHeight: 20, + marginBottom: 8, + }, + link: { + color: '#6666aa', + fontSize: 12, + marginBottom: 28, + }, + button: { + alignItems: 'center', + alignSelf: 'flex-start', + backgroundColor: '#4a90d9', + borderRadius: 8, + paddingHorizontal: 18, + paddingVertical: 12, + }, + buttonDown: { + backgroundColor: '#3978b8', + }, + buttonText: { + color: '#fff', + fontSize: 15, + fontWeight: '700', + }, +}); diff --git a/example/app/issues/45/modal.tsx b/example/app/issues/45/modal.tsx new file mode 100644 index 0000000..6c2ed95 --- /dev/null +++ b/example/app/issues/45/modal.tsx @@ -0,0 +1,128 @@ +import { StyleSheet, Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { EaseView } from 'react-native-ease'; + +export default function Issue45Modal() { + const insets = useSafeAreaInsets(); + + return ( + + Modal repro + + Dismiss this screen. If the bug is present on Android, one or both card + backgrounds disappear during the closing transition while the blue child + remains visible. The animated card should freeze in its current frame + during the close transition. + + + + + Animated background + + + + + + + Style background + + + + + + + Active animation on close + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + backgroundColor: '#1a1a2e', + gap: 16, + justifyContent: 'center', + paddingHorizontal: 20, + }, + heading: { + color: '#fff', + fontSize: 22, + fontWeight: '700', + }, + body: { + color: '#aaaacc', + fontSize: 14, + lineHeight: 20, + maxWidth: 340, + textAlign: 'center', + }, + cards: { + alignItems: 'center', + gap: 16, + marginTop: 12, + }, + demo: { + alignItems: 'center', + gap: 8, + }, + label: { + color: '#8888aa', + fontSize: 13, + }, + card: { + alignItems: 'center', + height: 100, + justifyContent: 'center', + width: 100, + }, + card2: { + alignItems: 'center', + backgroundColor: 'green', + height: 100, + justifyContent: 'center', + width: 100, + }, + child: { + backgroundColor: 'blue', + height: 24, + width: 24, + }, + spinCard: { + alignItems: 'center', + backgroundColor: '#4a90d9', + borderRadius: 12, + height: 100, + justifyContent: 'flex-start', + paddingTop: 10, + width: 100, + }, + spinMarker: { + backgroundColor: '#fff', + borderRadius: 6, + height: 12, + width: 12, + }, +}); diff --git a/example/src/demos/index.ts b/example/src/demos/index.ts index 3fcc18a..802d658 100644 --- a/example/src/demos/index.ts +++ b/example/src/demos/index.ts @@ -142,6 +142,11 @@ export const demos: Record = { title: 'Issue #42 — Tabs loop', section: 'Issues', }, + 'issue-45': { + route: '/issues/45', + title: 'Issue #45 — Android modal background', + section: 'Issues', + }, }; interface SectionData { From 46562815a6450faf57a069c529f6bbde51d950ac Mon Sep 17 00:00:00 2001 From: Melissa Vallee Date: Thu, 11 Jun 2026 16:20:00 -0400 Subject: [PATCH 2/2] test: remove extra animation from Issue 45 repro --- .../src/main/java/com/ease/EaseViewManager.kt | 2 ++ example/app/issues/45/modal.tsx | 35 +------------------ 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/android/src/main/java/com/ease/EaseViewManager.kt b/android/src/main/java/com/ease/EaseViewManager.kt index 725d924..5d59a57 100644 --- a/android/src/main/java/com/ease/EaseViewManager.kt +++ b/android/src/main/java/com/ease/EaseViewManager.kt @@ -225,6 +225,8 @@ class EaseViewManager : ReactViewManager() { override fun onDropViewInstance(view: ReactViewGroup) { super.onDropViewInstance(view) + // Android screen transitions may keep drawing dropped views briefly. + // Stop animation work here, but keep visual state until recycle. (view as? EaseView)?.stopAnimations() } diff --git a/example/app/issues/45/modal.tsx b/example/app/issues/45/modal.tsx index 6c2ed95..3c08339 100644 --- a/example/app/issues/45/modal.tsx +++ b/example/app/issues/45/modal.tsx @@ -11,8 +11,7 @@ export default function Issue45Modal() { Dismiss this screen. If the bug is present on Android, one or both card backgrounds disappear during the closing transition while the blue child - remains visible. The animated card should freeze in its current frame - during the close transition. + remains visible. @@ -36,23 +35,6 @@ export default function Issue45Modal() { - - - Active animation on close - - - - ); @@ -110,19 +92,4 @@ const styles = StyleSheet.create({ height: 24, width: 24, }, - spinCard: { - alignItems: 'center', - backgroundColor: '#4a90d9', - borderRadius: 12, - height: 100, - justifyContent: 'flex-start', - paddingTop: 10, - width: 100, - }, - spinMarker: { - backgroundColor: '#fff', - borderRadius: 6, - height: 12, - width: 12, - }, });