From 0dfc39842459daea98e2b551bbecd16d1baca439 Mon Sep 17 00:00:00 2001 From: Chet Haase Date: Fri, 28 Jan 2011 14:13:22 -0800 Subject: [PATCH] Fixed LayoutTransition bug moving multiple views The problem was that there can be >1 animation spawned for each view in a container, if there are multiple events that trigger a transition. These animations would potentially clobber object layout values, causing problems as successive animations tried to use those clobbered values to set up their own animation values. The fix is to track the created animations and cancel them as future animations on those same objects get created. This mechanism used to be in the code (the bug came about when that mechanism went away), but was removed because of memory leaks of never removing animations that were set up but never started. The new approach also caches pending animations, but runs a second aniamtor to delete the entries in that collection just in case. Change-Id: If60c7d188712334dea69d0794dc6b4ce29ca6c09 --- .../android/animation/LayoutTransition.java | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index f13d940df3545..d3e10f3940076 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -166,6 +166,7 @@ public class LayoutTransition { * we cache all of the current animations in this map for possible cancellation on * another layout event. */ + private final HashMap pendingAnimations = new HashMap(); private final HashMap currentChangingAnimations = new HashMap(); private final HashMap currentVisibilityAnimations = new HashMap(); @@ -542,6 +543,8 @@ public class LayoutTransition { // reset the inter-animation delay, in case we use it later staggerDelay = 0; + final long duration = (changeReason == APPEARING) ? + mChangingAppearingDuration : mChangingDisappearingDuration; final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup if (!observer.isAlive()) { @@ -556,12 +559,6 @@ public class LayoutTransition { // only animate the views not being added or removed if (child != newView) { - // If there's an animation running on this view already, cancel it - Animator currentAnimation = currentChangingAnimations.get(child); - if (currentAnimation != null) { - currentAnimation.cancel(); - currentChangingAnimations.remove(child); - } // Make a copy of the appropriate animation final Animator anim = baseAnimator.clone(); @@ -573,6 +570,30 @@ public class LayoutTransition { // its target object anim.setupStartValues(); + // If there's an animation running on this view already, cancel it + Animator currentAnimation = pendingAnimations.get(child); + if (currentAnimation != null) { + currentAnimation.cancel(); + pendingAnimations.remove(child); + } + // Cache the animation in case we need to cancel it later + pendingAnimations.put(child, anim); + + // For the animations which don't get started, we have to have a means of + // removing them from the cache, lest we leak them and their target objects. + // We run an animator for the default duration+100 (an arbitrary time, but one + // which should far surpass the delay between setting them up here and + // handling layout events which start them. + ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f). + setDuration(duration+100); + pendingAnimRemover.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + pendingAnimations.remove(child); + } + }); + pendingAnimRemover.start(); + // Add a listener to track layout changes on this view. If we don't get a callback, // then there's nothing to animate. final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { @@ -583,19 +604,25 @@ public class LayoutTransition { anim.setupEndValues(); long startDelay; - long duration; if (changeReason == APPEARING) { startDelay = mChangingAppearingDelay + staggerDelay; staggerDelay += mChangingAppearingStagger; - duration = mChangingAppearingDuration; } else { startDelay = mChangingDisappearingDelay + staggerDelay; staggerDelay += mChangingDisappearingStagger; - duration = mChangingDisappearingDuration; } anim.setStartDelay(startDelay); anim.setDuration(duration); + Animator prevAnimation = currentChangingAnimations.get(child); + if (prevAnimation != null) { + prevAnimation.cancel(); + currentChangingAnimations.remove(child); + } + Animator pendingAnimation = pendingAnimations.get(child); + if (pendingAnimation != null) { + pendingAnimations.remove(child); + } // Cache the animation in case we need to cancel it later currentChangingAnimations.put(child, anim);