From d2d5cdff44a7d731bc38c0862878fda21c5d1cbe Mon Sep 17 00:00:00 2001 From: Andrey Kulikov Date: Thu, 11 Oct 2018 14:13:29 +0100 Subject: [PATCH] Fix crash in RecyclerView if it's view is animated by Transitions When it is happening: a) Disappear Visibility transition is applied to the recyclerview(and it's children) b) Transition added the View to the ViewOverlay for an animation c) Transition is first paused before being canceled (for example when the new reversed transition wants to start after user click) d) In Visibility.onPause() we call suppressLayout(false) for RecyclerView e) RecyclerView starts layouting and tries to use our view, but it is currently added to the overlay f) So it crashes on attempt to call addView Fix: Detach a view from overlay in Visibility transition while it is paused. Attach it back in onTransitionResume if the view is still not used by someone else like RecyclerView. Bug: 33609996 Fix in AndroidX: I18d8327b338be442ec30b15fe53a99d1a2974888 Test: cts tests for Transitions Change-Id: I74f138617c8afbac9f6efa4ee9a1f4e961306c9e --- core/java/android/transition/Visibility.java | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 319f080bbe633..bd2bef4f11ae4 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -25,6 +25,7 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroupOverlay; import com.android.internal.R; @@ -413,7 +414,6 @@ public abstract class Visibility extends Transition { } } final int finalVisibility = endVisibility; - final ViewGroup finalSceneRoot = sceneRoot; if (overlayView != null) { // TODO: Need to do this for general case of adding to overlay @@ -424,16 +424,32 @@ public abstract class Visibility extends Transition { sceneRoot.getLocationOnScreen(loc); overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft()); overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop()); - sceneRoot.getOverlay().add(overlayView); + final ViewGroupOverlay overlay = sceneRoot.getOverlay(); + overlay.add(overlayView); Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues); if (animator == null) { - sceneRoot.getOverlay().remove(overlayView); + overlay.remove(overlayView); } else { final View finalOverlayView = overlayView; addListener(new TransitionListenerAdapter() { + + @Override + public void onTransitionPause(Transition transition) { + overlay.remove(finalOverlayView); + } + + @Override + public void onTransitionResume(Transition transition) { + if (finalOverlayView.getParent() == null) { + overlay.add(finalOverlayView); + } else { + cancel(); + } + } + @Override public void onTransitionEnd(Transition transition) { - finalSceneRoot.getOverlay().remove(finalOverlayView); + overlay.remove(finalOverlayView); transition.removeListener(this); } });