Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +1 −2 Original line number Diff line number Diff line Loading @@ -324,8 +324,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (updatePosition && !mIsExpanded) { // If alerting it gets promoted to top of the stack. if (mBubbleContainer.indexOfChild(bubbleView) != 0) { mBubbleContainer.removeViewAndThen(bubbleView, () -> mBubbleContainer.addView(bubbleView, 0)); mBubbleContainer.moveViewTo(bubbleView, 0); } requestUpdate(); } Loading packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +85 −68 Original line number Diff line number Diff line Loading @@ -27,9 +27,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.HashSet; import java.util.Set; /** Loading Loading @@ -122,12 +121,8 @@ public class PhysicsAnimationLayout extends FrameLayout { protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> mEndListenerForProperty = new HashMap<>(); /** * List of views that were passed to removeView, but are currently being animated out. These * views will be actually removed by the controller (via super.removeView) once they're done * animating out. */ private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); /** Set of currently rendered transient views. */ private final Set<View> mTransientViews = new HashSet<>(); /** The currently active animation controller. */ private PhysicsAnimationController mController; Loading Loading @@ -186,23 +181,6 @@ public class PhysicsAnimationLayout extends FrameLayout { mEndListenerForProperty.remove(property); } /** * Returns the index of the view that precedes the given index, ignoring views that were passed * to removeView, but are currently being animated out before actually being removed. * * @return index of the preceding view, or -1 if there are none. */ public int getPrecedingNonRemovedViewIndex(int index) { for (int i = index + 1; i < getChildCount(); i++) { View precedingView = getChildAt(i); if (!mViewsToBeActuallyRemoved.contains(precedingView)) { return i; } } return -1; } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); Loading @@ -224,6 +202,24 @@ public class PhysicsAnimationLayout extends FrameLayout { removeViewAndThen(view, /* callback */ null); } @Override public void addTransientView(View view, int index) { super.addTransientView(view, index); mTransientViews.add(view); } @Override public void removeTransientView(View view) { super.removeTransientView(view); mTransientViews.remove(view); } /** Immediately moves the view from wherever it currently is, to the given index. */ public void moveViewTo(View view, int index) { super.removeView(view); addView(view, index); } /** * Let the controller know that this view should be removed, and then call the callback once the * controller has finished any removal animations and the view has actually been removed. Loading @@ -231,23 +227,31 @@ public class PhysicsAnimationLayout extends FrameLayout { public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { final int index = indexOfChild(view); // Remove the view only if it exists in this layout, and we're not already working on // animating its removal. if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { mViewsToBeActuallyRemoved.add(view); // Remove the view and add it back as a transient view so we can animate it out. super.removeView(view); addTransientView(view, index); setChildrenVisibility(); // Tell the controller to animate this view out, and call the callback when it wants // to actually remove the view. // Tell the controller to animate this view out, and call the callback when it's // finished. mController.onChildToBeRemoved(view, index, () -> { removeViewImmediateAndThen(view, callback); mViewsToBeActuallyRemoved.remove(view); }); // Done animating, remove the transient view. removeTransientView(view); if (callback != null) { callback.run(); } }); } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. removeViewImmediateAndThen(view, callback); super.removeView(view); if (callback != null) { callback.run(); } } } Loading Loading @@ -278,17 +282,18 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** * Animates the property of the child at the given index to the given value, then runs the * callback provided when the animation ends. * Animates the property of the given child view, then runs the callback provided when the * animation ends. */ protected void animateValueForChildAtIndex( protected void animateValueForChild( DynamicAnimation.ViewProperty property, int index, View view, float value, float startVel, Runnable after) { if (index < getChildCount()) { final SpringAnimation animation = getAnimationAtIndex(property, index); if (view != null) { final SpringAnimation animation = (SpringAnimation) view.getTag(getTagIdForProperty(property)); if (after != null) { animation.addEndListener(new OneTimeEndListener() { @Override Loading @@ -300,12 +305,36 @@ public class PhysicsAnimationLayout extends FrameLayout { }); } if (startVel != Float.MAX_VALUE) { animation.setStartVelocity(startVel); animation.animateToFinalPosition(value); } } animation.animateToFinalPosition(value); protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value, Runnable after) { animateValueForChild(property, view, value, Float.MAX_VALUE, after); } protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value) { animateValueForChild(property, view, value, Float.MAX_VALUE, /* after */ null); } /** * Animates the property of the child at the given index to the given value, then runs the * callback provided when the animation ends. */ protected void animateValueForChildAtIndex( DynamicAnimation.ViewProperty property, int index, float value, float startVel, Runnable after) { animateValueForChild(property, getChildAt(index), value, startVel, after); } /** Shortcut to animate a value with a callback, but no start velocity. */ Loading Loading @@ -366,18 +395,6 @@ public class PhysicsAnimationLayout extends FrameLayout { } } /** Immediately removes the view, without notifying the controller, then runs the callback. */ private void removeViewImmediateAndThen(View view, Runnable callback) { super.removeView(view); if (callback != null) { callback.run(); } setChildrenVisibility(); } /** * Retrieves the animation of the given property from the view at the given index via the view * tag system. Loading @@ -401,7 +418,11 @@ public class PhysicsAnimationLayout extends FrameLayout { newAnim.addUpdateListener((animation, value, velocity) -> { final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild(child)); if (nextAnimInChain == PhysicsAnimationController.NONE) { // If the controller doesn't want us to chain, or if we're a transient view in the // process of being removed, don't chain. if (nextAnimInChain == PhysicsAnimationController.NONE || mTransientViews.contains(child)) { return; } Loading @@ -412,9 +433,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // If this property's animations should be chained, then check to see if there is a // subsequent animation within the rendering limit, and if so, tell it to animate to // this animation's new value (plus the offset). if (nextAnimInChain < Math.min( getChildCount(), mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { if (nextAnimInChain < Math.min(getChildCount(), mMaxRenderedChildren)) { getAnimationAtIndex(property, animIndex + 1) .animateToFinalPosition(value + offset); } else if (nextAnimInChain < getChildCount()) { Loading Loading @@ -442,9 +461,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // Ignore views that are animating out when calculating whether to hide the // view. That is, if we're supposed to render 5 views, but 4 are animating out // and will soon be removed, render up to 9 views temporarily. i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) ? View.VISIBLE : View.GONE); i < mMaxRenderedChildren ? View.VISIBLE : View.GONE); } } Loading packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +16 −24 Original line number Diff line number Diff line Loading @@ -53,8 +53,8 @@ public class StackAnimationController extends /** Scale factor to use initially for new bubbles being animated in. */ private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */ private static final int ANIMATE_TRANSLATION_FACTOR = 4; /** * Values to use for the default {@link SpringForce} provided to the physics animation layout. Loading Loading @@ -309,7 +309,7 @@ public class StackAnimationController extends // animate in from this position. Since the animations are chained, when the new bubble // flies in from the side, it will push the other ones out of the way. float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); child.setTranslationX(mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset); mLayout.animateValueForChildAtIndex( DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); } Loading @@ -318,27 +318,19 @@ public class StackAnimationController extends @Override void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { // Animate the child out, actually removing it once its alpha is zero. mLayout.animateValueForChildAtIndex( DynamicAnimation.ALPHA, index, 0f, () -> { actuallyRemove.run(); }); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChild( DynamicAnimation.ALPHA, child, 0f, actuallyRemove); mLayout.animateValueForChild(DynamicAnimation.SCALE_X, child, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChild(DynamicAnimation.SCALE_Y, child, ANIMATE_IN_STARTING_SCALE); final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); if (hasPrecedingChild) { final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); if (precedingViewIndex >= 0) { final float offsetX = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); mLayout.animatePositionForChildAtIndex( precedingViewIndex, mStackPosition.x + (index * offsetX), mStackPosition.y); } } // Animate the removing view in the opposite direction of the stack. final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); mLayout.animateValueForChild(DynamicAnimation.TRANSLATION_X, child, mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR)); // Pull the top of the stack to the correct position, the chained animations will instruct // any children that are out of place to animate to the correct position. mLayout.animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); } /** Moves the stack, without any animation, to the starting position. */ Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +2 −24 Original line number Diff line number Diff line Loading @@ -75,7 +75,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Test public void testRenderVisibility() { public void testRenderVisibility() throws InterruptedException { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); Loading @@ -87,7 +87,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Test public void testHierarchyChanges() { public void testHierarchyChanges() throws InterruptedException { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); Loading Loading @@ -241,26 +241,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { endListenerCalls.verifyNoMoreInteractions(); } @Test public void testPrecedingNonRemovedIndex() { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); // Call removeView at index 4, but don't actually remove it yet (as if we're animating it // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since // 4 is on its way out. assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); mLayout.removeView(mViews.get(4)); assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); // Call removeView at index 1, and actually remove it immediately. With the old view at 1 // instantly gone, the preceding view to 0 should be 1 in both cases. assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); mTestableController.setRemoveImmediately(true); mLayout.removeView(mViews.get(1)); assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); } @Test public void testSetController() throws InterruptedException { // Add the bubbles, then set the controller, to make sure that a controller added to an Loading Loading @@ -360,8 +340,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { mLayout.cancelAllAnimations(); waitForLayoutMessageQueue(); // Animations should be somewhere before their end point. assertTrue(mViews.get(0).getTranslationX() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000); Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +30 −7 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Looper; import android.view.DisplayCutout; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.widget.FrameLayout; Loading Loading @@ -93,7 +94,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ void addOneMoreThanRenderLimitBubbles() { void addOneMoreThanRenderLimitBubbles() throws InterruptedException { for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { final View newView = new FrameLayout(mContext); mLayout.addView(newView, 0); Loading Loading @@ -129,7 +130,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { void waitForLayoutMessageQueue() throws InterruptedException { // Wait for layout, then the view should be actually removed. CountDownLatch layoutLatch = new CountDownLatch(1); mLayout.post(layoutLatch::countDown); mMainThreadHandler.post(layoutLatch::countDown); layoutLatch.await(1, TimeUnit.SECONDS); } Loading @@ -145,11 +146,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { @Override public void setController(PhysicsAnimationController controller) { mMainThreadHandler.post(() -> super.setController(controller)); try { waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } waitForMessageQueueAndIgnoreIfInterrupted(); } @Override Loading @@ -169,6 +166,32 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { return mWindowInsets; } @Override public void removeView(View view) { mMainThreadHandler.post(() -> super.removeView(view)); waitForMessageQueueAndIgnoreIfInterrupted(); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { mMainThreadHandler.post(() -> super.addView(child, index, params)); waitForMessageQueueAndIgnoreIfInterrupted(); } /** * Wait for the queue but just catch and print the exception if interrupted, since we can't * just add the exception to the overridden methods' signatures. */ private void waitForMessageQueueAndIgnoreIfInterrupted() { try { waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Sets an end listener that will be called after the 'real' end listener that was already * set. Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +1 −2 Original line number Diff line number Diff line Loading @@ -324,8 +324,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (updatePosition && !mIsExpanded) { // If alerting it gets promoted to top of the stack. if (mBubbleContainer.indexOfChild(bubbleView) != 0) { mBubbleContainer.removeViewAndThen(bubbleView, () -> mBubbleContainer.addView(bubbleView, 0)); mBubbleContainer.moveViewTo(bubbleView, 0); } requestUpdate(); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +85 −68 Original line number Diff line number Diff line Loading @@ -27,9 +27,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.HashSet; import java.util.Set; /** Loading Loading @@ -122,12 +121,8 @@ public class PhysicsAnimationLayout extends FrameLayout { protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> mEndListenerForProperty = new HashMap<>(); /** * List of views that were passed to removeView, but are currently being animated out. These * views will be actually removed by the controller (via super.removeView) once they're done * animating out. */ private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); /** Set of currently rendered transient views. */ private final Set<View> mTransientViews = new HashSet<>(); /** The currently active animation controller. */ private PhysicsAnimationController mController; Loading Loading @@ -186,23 +181,6 @@ public class PhysicsAnimationLayout extends FrameLayout { mEndListenerForProperty.remove(property); } /** * Returns the index of the view that precedes the given index, ignoring views that were passed * to removeView, but are currently being animated out before actually being removed. * * @return index of the preceding view, or -1 if there are none. */ public int getPrecedingNonRemovedViewIndex(int index) { for (int i = index + 1; i < getChildCount(); i++) { View precedingView = getChildAt(i); if (!mViewsToBeActuallyRemoved.contains(precedingView)) { return i; } } return -1; } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); Loading @@ -224,6 +202,24 @@ public class PhysicsAnimationLayout extends FrameLayout { removeViewAndThen(view, /* callback */ null); } @Override public void addTransientView(View view, int index) { super.addTransientView(view, index); mTransientViews.add(view); } @Override public void removeTransientView(View view) { super.removeTransientView(view); mTransientViews.remove(view); } /** Immediately moves the view from wherever it currently is, to the given index. */ public void moveViewTo(View view, int index) { super.removeView(view); addView(view, index); } /** * Let the controller know that this view should be removed, and then call the callback once the * controller has finished any removal animations and the view has actually been removed. Loading @@ -231,23 +227,31 @@ public class PhysicsAnimationLayout extends FrameLayout { public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { final int index = indexOfChild(view); // Remove the view only if it exists in this layout, and we're not already working on // animating its removal. if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { mViewsToBeActuallyRemoved.add(view); // Remove the view and add it back as a transient view so we can animate it out. super.removeView(view); addTransientView(view, index); setChildrenVisibility(); // Tell the controller to animate this view out, and call the callback when it wants // to actually remove the view. // Tell the controller to animate this view out, and call the callback when it's // finished. mController.onChildToBeRemoved(view, index, () -> { removeViewImmediateAndThen(view, callback); mViewsToBeActuallyRemoved.remove(view); }); // Done animating, remove the transient view. removeTransientView(view); if (callback != null) { callback.run(); } }); } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. removeViewImmediateAndThen(view, callback); super.removeView(view); if (callback != null) { callback.run(); } } } Loading Loading @@ -278,17 +282,18 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** * Animates the property of the child at the given index to the given value, then runs the * callback provided when the animation ends. * Animates the property of the given child view, then runs the callback provided when the * animation ends. */ protected void animateValueForChildAtIndex( protected void animateValueForChild( DynamicAnimation.ViewProperty property, int index, View view, float value, float startVel, Runnable after) { if (index < getChildCount()) { final SpringAnimation animation = getAnimationAtIndex(property, index); if (view != null) { final SpringAnimation animation = (SpringAnimation) view.getTag(getTagIdForProperty(property)); if (after != null) { animation.addEndListener(new OneTimeEndListener() { @Override Loading @@ -300,12 +305,36 @@ public class PhysicsAnimationLayout extends FrameLayout { }); } if (startVel != Float.MAX_VALUE) { animation.setStartVelocity(startVel); animation.animateToFinalPosition(value); } } animation.animateToFinalPosition(value); protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value, Runnable after) { animateValueForChild(property, view, value, Float.MAX_VALUE, after); } protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value) { animateValueForChild(property, view, value, Float.MAX_VALUE, /* after */ null); } /** * Animates the property of the child at the given index to the given value, then runs the * callback provided when the animation ends. */ protected void animateValueForChildAtIndex( DynamicAnimation.ViewProperty property, int index, float value, float startVel, Runnable after) { animateValueForChild(property, getChildAt(index), value, startVel, after); } /** Shortcut to animate a value with a callback, but no start velocity. */ Loading Loading @@ -366,18 +395,6 @@ public class PhysicsAnimationLayout extends FrameLayout { } } /** Immediately removes the view, without notifying the controller, then runs the callback. */ private void removeViewImmediateAndThen(View view, Runnable callback) { super.removeView(view); if (callback != null) { callback.run(); } setChildrenVisibility(); } /** * Retrieves the animation of the given property from the view at the given index via the view * tag system. Loading @@ -401,7 +418,11 @@ public class PhysicsAnimationLayout extends FrameLayout { newAnim.addUpdateListener((animation, value, velocity) -> { final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild(child)); if (nextAnimInChain == PhysicsAnimationController.NONE) { // If the controller doesn't want us to chain, or if we're a transient view in the // process of being removed, don't chain. if (nextAnimInChain == PhysicsAnimationController.NONE || mTransientViews.contains(child)) { return; } Loading @@ -412,9 +433,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // If this property's animations should be chained, then check to see if there is a // subsequent animation within the rendering limit, and if so, tell it to animate to // this animation's new value (plus the offset). if (nextAnimInChain < Math.min( getChildCount(), mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { if (nextAnimInChain < Math.min(getChildCount(), mMaxRenderedChildren)) { getAnimationAtIndex(property, animIndex + 1) .animateToFinalPosition(value + offset); } else if (nextAnimInChain < getChildCount()) { Loading Loading @@ -442,9 +461,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // Ignore views that are animating out when calculating whether to hide the // view. That is, if we're supposed to render 5 views, but 4 are animating out // and will soon be removed, render up to 9 views temporarily. i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) ? View.VISIBLE : View.GONE); i < mMaxRenderedChildren ? View.VISIBLE : View.GONE); } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +16 −24 Original line number Diff line number Diff line Loading @@ -53,8 +53,8 @@ public class StackAnimationController extends /** Scale factor to use initially for new bubbles being animated in. */ private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */ private static final int ANIMATE_TRANSLATION_FACTOR = 4; /** * Values to use for the default {@link SpringForce} provided to the physics animation layout. Loading Loading @@ -309,7 +309,7 @@ public class StackAnimationController extends // animate in from this position. Since the animations are chained, when the new bubble // flies in from the side, it will push the other ones out of the way. float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); child.setTranslationX(mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset); mLayout.animateValueForChildAtIndex( DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); } Loading @@ -318,27 +318,19 @@ public class StackAnimationController extends @Override void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { // Animate the child out, actually removing it once its alpha is zero. mLayout.animateValueForChildAtIndex( DynamicAnimation.ALPHA, index, 0f, () -> { actuallyRemove.run(); }); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChild( DynamicAnimation.ALPHA, child, 0f, actuallyRemove); mLayout.animateValueForChild(DynamicAnimation.SCALE_X, child, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChild(DynamicAnimation.SCALE_Y, child, ANIMATE_IN_STARTING_SCALE); final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); if (hasPrecedingChild) { final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); if (precedingViewIndex >= 0) { final float offsetX = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); mLayout.animatePositionForChildAtIndex( precedingViewIndex, mStackPosition.x + (index * offsetX), mStackPosition.y); } } // Animate the removing view in the opposite direction of the stack. final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); mLayout.animateValueForChild(DynamicAnimation.TRANSLATION_X, child, mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR)); // Pull the top of the stack to the correct position, the chained animations will instruct // any children that are out of place to animate to the correct position. mLayout.animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); } /** Moves the stack, without any animation, to the starting position. */ Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +2 −24 Original line number Diff line number Diff line Loading @@ -75,7 +75,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Test public void testRenderVisibility() { public void testRenderVisibility() throws InterruptedException { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); Loading @@ -87,7 +87,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Test public void testHierarchyChanges() { public void testHierarchyChanges() throws InterruptedException { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); Loading Loading @@ -241,26 +241,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { endListenerCalls.verifyNoMoreInteractions(); } @Test public void testPrecedingNonRemovedIndex() { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); // Call removeView at index 4, but don't actually remove it yet (as if we're animating it // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since // 4 is on its way out. assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); mLayout.removeView(mViews.get(4)); assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); // Call removeView at index 1, and actually remove it immediately. With the old view at 1 // instantly gone, the preceding view to 0 should be 1 in both cases. assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); mTestableController.setRemoveImmediately(true); mLayout.removeView(mViews.get(1)); assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); } @Test public void testSetController() throws InterruptedException { // Add the bubbles, then set the controller, to make sure that a controller added to an Loading Loading @@ -360,8 +340,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { mLayout.cancelAllAnimations(); waitForLayoutMessageQueue(); // Animations should be somewhere before their end point. assertTrue(mViews.get(0).getTranslationX() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000); Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +30 −7 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Looper; import android.view.DisplayCutout; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.widget.FrameLayout; Loading Loading @@ -93,7 +94,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ void addOneMoreThanRenderLimitBubbles() { void addOneMoreThanRenderLimitBubbles() throws InterruptedException { for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { final View newView = new FrameLayout(mContext); mLayout.addView(newView, 0); Loading Loading @@ -129,7 +130,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { void waitForLayoutMessageQueue() throws InterruptedException { // Wait for layout, then the view should be actually removed. CountDownLatch layoutLatch = new CountDownLatch(1); mLayout.post(layoutLatch::countDown); mMainThreadHandler.post(layoutLatch::countDown); layoutLatch.await(1, TimeUnit.SECONDS); } Loading @@ -145,11 +146,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { @Override public void setController(PhysicsAnimationController controller) { mMainThreadHandler.post(() -> super.setController(controller)); try { waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } waitForMessageQueueAndIgnoreIfInterrupted(); } @Override Loading @@ -169,6 +166,32 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { return mWindowInsets; } @Override public void removeView(View view) { mMainThreadHandler.post(() -> super.removeView(view)); waitForMessageQueueAndIgnoreIfInterrupted(); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { mMainThreadHandler.post(() -> super.addView(child, index, params)); waitForMessageQueueAndIgnoreIfInterrupted(); } /** * Wait for the queue but just catch and print the exception if interrupted, since we can't * just add the exception to the overridden methods' signatures. */ private void waitForMessageQueueAndIgnoreIfInterrupted() { try { waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Sets an end listener that will be called after the 'real' end listener that was already * set. Loading