Merge changes I1360deb0,Id9a9eb85,I7f05f2ee,Ie48da88d into qt-dev

* changes:
  Fix issues with animations when the stack is expanded.
  Prevent a crash when touching the stack while it's flinging out of the dismiss area.
  Remove the max rendered children code.
  Stability and logic fixes to PhysicsAnimationLayout to address bugs that leave Bubbles in a bad state.
This commit is contained in:
Josh Tsuji
2019-05-30 18:23:26 +00:00
committed by Android (Google) Code Review
8 changed files with 371 additions and 341 deletions

View File

@@ -330,9 +330,7 @@ public class BubbleStackView extends FrameLayout {
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER; mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
mBubbleContainer = new PhysicsAnimationLayout(context); mBubbleContainer = new PhysicsAnimationLayout(context);
mBubbleContainer.setMaxRenderedChildren( mBubbleContainer.setActiveController(mStackAnimationController);
getResources().getInteger(R.integer.bubbles_max_rendered));
mBubbleContainer.setController(mStackAnimationController);
mBubbleContainer.setElevation(elevation); mBubbleContainer.setElevation(elevation);
mBubbleContainer.setClipChildren(false); mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@@ -728,7 +726,7 @@ public class BubbleStackView extends FrameLayout {
public void updateBubbleOrder(List<Bubble> bubbles) { public void updateBubbleOrder(List<Bubble> bubbles) {
for (int i = 0; i < bubbles.size(); i++) { for (int i = 0; i < bubbles.size(); i++) {
Bubble bubble = bubbles.get(i); Bubble bubble = bubbles.get(i);
mBubbleContainer.moveViewTo(bubble.iconView, i); mBubbleContainer.reorderView(bubble.iconView, i);
} }
} }
@@ -908,19 +906,17 @@ public class BubbleStackView extends FrameLayout {
}; };
if (shouldExpand) { if (shouldExpand) {
mBubbleContainer.setController(mExpandedAnimationController); mBubbleContainer.setActiveController(mExpandedAnimationController);
mExpandedAnimationController.expandFromStack( mExpandedAnimationController.expandFromStack(() -> {
mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() updatePointerPosition();
/* collapseTo */, updateAfter.run();
() -> { } /* after */);
updatePointerPosition();
updateAfter.run();
} /* after */);
} else { } else {
mBubbleContainer.cancelAllAnimations(); mBubbleContainer.cancelAllAnimations();
mExpandedAnimationController.collapseBackToStack( mExpandedAnimationController.collapseBackToStack(
mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
() -> { () -> {
mBubbleContainer.setController(mStackAnimationController); mBubbleContainer.setActiveController(mStackAnimationController);
updateAfter.run(); updateAfter.run();
}); });
} }
@@ -1013,7 +1009,7 @@ public class BubbleStackView extends FrameLayout {
} }
mStackAnimationController.cancelStackPositionAnimations(); mStackAnimationController.cancelStackPositionAnimations();
mBubbleContainer.setController(mStackAnimationController); mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate(); hideFlyoutImmediate();
mDraggingInDismissTarget = false; mDraggingInDismissTarget = false;
@@ -1111,6 +1107,10 @@ public class BubbleStackView extends FrameLayout {
/** Called when a gesture is completed or cancelled. */ /** Called when a gesture is completed or cancelled. */
void onGestureFinished() { void onGestureFinished() {
mIsGestureInProgress = false; mIsGestureInProgress = false;
if (mIsExpanded) {
mExpandedAnimationController.onGestureFinished();
}
} }
/** Prepares and starts the desaturate/darken animation on the bubble stack. */ /** Prepares and starts the desaturate/darken animation on the bubble stack. */
@@ -1201,6 +1201,7 @@ public class BubbleStackView extends FrameLayout {
*/ */
void magnetToStackIfNeededThenAnimateDismissal( void magnetToStackIfNeededThenAnimateDismissal(
View touchedView, float velX, float velY, Runnable after) { View touchedView, float velX, float velY, Runnable after) {
final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble();
final Runnable animateDismissal = () -> { final Runnable animateDismissal = () -> {
mAfterMagnet = null; mAfterMagnet = null;
@@ -1218,7 +1219,7 @@ public class BubbleStackView extends FrameLayout {
resetDesaturationAndDarken(); resetDesaturationAndDarken();
}); });
} else { } else {
mExpandedAnimationController.dismissDraggedOutBubble(() -> { mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> {
mAnimatingMagnet = false; mAnimatingMagnet = false;
mShowingDismiss = false; mShowingDismiss = false;
mDraggingInDismissTarget = false; mDraggingInDismissTarget = false;
@@ -1385,10 +1386,18 @@ public class BubbleStackView extends FrameLayout {
}; };
// Post in case layout isn't complete and getWidth returns 0. // Post in case layout isn't complete and getWidth returns 0.
post(() -> mFlyout.showFlyout( post(() -> {
updateMessage, mStackAnimationController.getStackPosition(), getWidth(), // An auto-expanding bubble could have been posted during the time it takes to
mStackAnimationController.isStackOnLeftSide(), // layout.
bubble.iconView.getBadgeColor(), mAfterFlyoutHides)); if (isExpanded()) {
return;
}
mFlyout.showFlyout(
updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
mStackAnimationController.isStackOnLeftSide(),
bubble.iconView.getBadgeColor(), mAfterFlyoutHides);
});
} }
mFlyout.removeCallbacks(mHideFlyout); mFlyout.removeCallbacks(mHideFlyout);

View File

@@ -22,6 +22,7 @@ import android.graphics.PointF;
import android.view.View; import android.view.View;
import android.view.WindowInsets; import android.view.WindowInsets;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce; import androidx.dynamicanimation.animation.SpringForce;
@@ -63,12 +64,16 @@ public class ExpandedAnimationController
private Point mDisplaySize; private Point mDisplaySize;
/** Size of dismiss target at bottom of screen. */ /** Size of dismiss target at bottom of screen. */
private float mPipDismissHeight; private float mPipDismissHeight;
/** Max number of bubbles shown in row above expanded view.*/
private int mBubblesMaxRendered;
/** Whether the dragged-out bubble is in the dismiss target. */ /** Whether the dragged-out bubble is in the dismiss target. */
private boolean mIndividualBubbleWithinDismissTarget = false; private boolean mIndividualBubbleWithinDismissTarget = false;
private boolean mAnimatingExpand = false;
private boolean mAnimatingCollapse = false;
private Runnable mAfterExpand;
private Runnable mAfterCollapse;
private PointF mCollapsePoint;
/** /**
* Whether the dragged out bubble is springing towards the touch point, rather than using the * Whether the dragged out bubble is springing towards the touch point, rather than using the
* default behavior of moving directly to the touch point. * default behavior of moving directly to the touch point.
@@ -96,57 +101,61 @@ public class ExpandedAnimationController
/** The bubble currently being dragged out of the row (to potentially be dismissed). */ /** The bubble currently being dragged out of the row (to potentially be dismissed). */
private View mBubbleDraggingOut; private View mBubbleDraggingOut;
/**
* Drag velocities for the dragging-out bubble when the drag finished. These are used by
* {@link #onChildRemoved} to animate out the bubble while respecting touch velocity.
*/
private float mBubbleDraggingOutVelX;
private float mBubbleDraggingOutVelY;
@Override
protected void setLayout(PhysicsAnimationLayout layout) {
super.setLayout(layout);
final Resources res = layout.getResources();
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
}
/** /**
* Animates expanding the bubbles into a row along the top of the screen. * Animates expanding the bubbles into a row along the top of the screen.
*/ */
public void expandFromStack(PointF collapseTo, Runnable after) { public void expandFromStack(Runnable after) {
animationsForChildrenFromIndex( mAnimatingCollapse = false;
0, /* startIndex */ mAnimatingExpand = true;
new ChildAnimationConfigurator() { mAfterExpand = after;
@Override
public void configureAnimationForChildAtIndex(
int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) {
animation.position(getBubbleLeft(index), getExpandedY());
}
})
.startAll(after);
mCollapseToPoint = collapseTo; startOrUpdateExpandAnimation();
} }
/** Animate collapsing the bubbles back to their stacked position. */ /** Animate collapsing the bubbles back to their stacked position. */
public void collapseBackToStack(Runnable after) { public void collapseBackToStack(PointF collapsePoint, Runnable after) {
// Stack to the left if we're going to the left, or right if not. mAnimatingExpand = false;
final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapseToPoint.x) ? -1 : 1; mAnimatingCollapse = true;
mAfterCollapse = after;
mCollapsePoint = collapsePoint;
startOrUpdateCollapseAnimation();
}
private void startOrUpdateExpandAnimation() {
animationsForChildrenFromIndex( animationsForChildrenFromIndex(
0, /* startIndex */ 0, /* startIndex */
(index, animation) -> (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
.startAll(() -> {
mAnimatingExpand = false;
if (mAfterExpand != null) {
mAfterExpand.run();
}
mAfterExpand = null;
});
}
private void startOrUpdateCollapseAnimation() {
// Stack to the left if we're going to the left, or right if not.
final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
animationsForChildrenFromIndex(
0, /* startIndex */
(index, animation) -> {
animation.position( animation.position(
mCollapseToPoint.x + (sideMultiplier * index * mStackOffsetPx), mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
mCollapseToPoint.y)) mCollapsePoint.y);
.startAll(after /* endAction */); })
.startAll(() -> {
mAnimatingCollapse = false;
if (mAfterCollapse != null) {
mAfterCollapse.run();
}
mAfterCollapse = null;
});
} }
/** Prepares the given bubble to be dragged out. */ /** Prepares the given bubble to be dragged out. */
@@ -190,10 +199,10 @@ public class ExpandedAnimationController
} }
/** Plays a dismiss animation on the dragged out bubble. */ /** Plays a dismiss animation on the dragged out bubble. */
public void dismissDraggedOutBubble(Runnable after) { public void dismissDraggedOutBubble(View bubble, Runnable after) {
mIndividualBubbleWithinDismissTarget = false; mIndividualBubbleWithinDismissTarget = false;
animationForChild(mBubbleDraggingOut) animationForChild(bubble)
.withStiffness(SpringForce.STIFFNESS_HIGH) .withStiffness(SpringForce.STIFFNESS_HIGH)
.scaleX(1.1f) .scaleX(1.1f)
.scaleY(1.1f) .scaleY(1.1f)
@@ -203,6 +212,10 @@ public class ExpandedAnimationController
updateBubblePositions(); updateBubblePositions();
} }
@Nullable public View getDraggedOutBubble() {
return mBubbleDraggingOut;
}
/** Magnets the given bubble to the dismiss target. */ /** Magnets the given bubble to the dismiss target. */
public void magnetBubbleToDismiss( public void magnetBubbleToDismiss(
View bubbleView, float velX, float velY, float destY, Runnable after) { View bubbleView, float velX, float velY, float destY, Runnable after) {
@@ -241,24 +254,17 @@ public class ExpandedAnimationController
final int index = mLayout.indexOfChild(bubbleView); final int index = mLayout.indexOfChild(bubbleView);
animationForChildAtIndex(index) animationForChildAtIndex(index)
.position(getBubbleLeft(index), getExpandedY()) .position(getBubbleLeft(index), getExpandedY())
.withPositionStartVelocities(velX, velY) .withPositionStartVelocities(velX, velY)
.start(() -> bubbleView.setTranslationZ(0f) /* after */); .start(() -> bubbleView.setTranslationZ(0f) /* after */);
mBubbleDraggingOut = null;
mBubbleDraggedOutEnough = false;
updateBubblePositions(); updateBubblePositions();
} }
/** /** Resets bubble drag out gesture flags. */
* Sets configuration variables so that when the given bubble is removed, the animations are public void onGestureFinished() {
* started with the given velocities.
*/
public void prepareForDismissalWithVelocity(View bubbleView, float velX, float velY) {
mBubbleDraggingOut = bubbleView;
mBubbleDraggingOutVelX = velX;
mBubbleDraggingOutVelY = velY;
mBubbleDraggedOutEnough = false; mBubbleDraggedOutEnough = false;
mBubbleDraggingOut = null;
} }
/** /**
@@ -296,6 +302,23 @@ public class ExpandedAnimationController
: 0); : 0);
} }
@Override
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
final Resources res = layout.getResources();
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
// Ensure that all child views are at 1x scale, and visible, in case they were animating
// in.
mLayout.setVisibility(View.VISIBLE);
animationsForChildrenFromIndex(0 /* startIndex */, (index, animation) ->
animation.scaleX(1f).scaleY(1f).alpha(1f)).startAll();
}
@Override @Override
Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
return Sets.newHashSet( return Sets.newHashSet(
@@ -325,14 +348,21 @@ public class ExpandedAnimationController
@Override @Override
void onChildAdded(View child, int index) { void onChildAdded(View child, int index) {
child.setTranslationX(getXForChildAtIndex(index)); // If a bubble is added while the expand/collapse animations are playing, update the
// animation to include the new bubble.
animationForChild(child) if (mAnimatingExpand) {
.translationY( startOrUpdateExpandAnimation();
getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ } else if (mAnimatingCollapse) {
getExpandedY() /* to */) startOrUpdateCollapseAnimation();
.start(); } else {
updateBubblePositions(); child.setTranslationX(getXForChildAtIndex(index));
animationForChild(child)
.translationY(
getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
getExpandedY() /* to */)
.start();
updateBubblePositions();
}
} }
@Override @Override
@@ -357,19 +387,15 @@ public class ExpandedAnimationController
} }
@Override @Override
protected void setChildVisibility(View child, int index, int visibility) { void onChildReordered(View child, int oldIndex, int newIndex) {
if (visibility == View.VISIBLE) { updateBubblePositions();
// Set alpha to 0 but then become visible immediately so the animation is visible.
child.setAlpha(0f);
child.setVisibility(View.VISIBLE);
}
animationForChild(child)
.alpha(visibility == View.GONE ? 0f : 1f)
.start(() -> super.setChildVisibility(child, index, visibility) /* after */);
} }
private void updateBubblePositions() { private void updateBubblePositions() {
if (mAnimatingExpand || mAnimatingCollapse) {
return;
}
for (int i = 0; i < mLayout.getChildCount(); i++) { for (int i = 0; i < mLayout.getChildCount(); i++) {
final View bubble = mLayout.getChildAt(i); final View bubble = mLayout.getChildAt(i);
@@ -378,6 +404,7 @@ public class ExpandedAnimationController
if (bubble.equals(mBubbleDraggingOut)) { if (bubble.equals(mBubbleDraggingOut)) {
return; return;
} }
animationForChild(bubble) animationForChild(bubble)
.translationX(getBubbleLeft(i)) .translationX(getBubbleLeft(i))
.start(); .start();
@@ -403,10 +430,7 @@ public class ExpandedAnimationController
return 0; return 0;
} }
int bubbleCount = mLayout.getChildCount(); int bubbleCount = mLayout.getChildCount();
if (bubbleCount > mBubblesMaxRendered) {
// Only shown bubbles are relevant for calculating position.
bubbleCount = mBubblesMaxRendered;
}
// Width calculations. // Width calculations.
double bubble = bubbleCount * mBubbleSizePx; double bubble = bubbleCount * mBubbleSizePx;
float gap = (bubbleCount - 1) * mBubblePaddingPx; float gap = (bubbleCount - 1) * mBubblePaddingPx;

View File

@@ -17,10 +17,12 @@
package com.android.systemui.bubbles.animation; package com.android.systemui.bubbles.animation;
import android.content.Context; import android.content.Context;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce; import androidx.dynamicanimation.animation.SpringForce;
@@ -137,27 +139,39 @@ public class PhysicsAnimationLayout extends FrameLayout {
*/ */
abstract void onChildRemoved(View child, int index, Runnable finishRemoval); abstract void onChildRemoved(View child, int index, Runnable finishRemoval);
/** Called when a child view has been reordered in the view hierachy. */
abstract void onChildReordered(View child, int oldIndex, int newIndex);
/**
* Called when the controller is set as the active animation controller for the given
* layout. Once active, the controller can start animations using the animator instances
* returned by {@link #animationForChild}.
*
* While all animations started by the previous controller will be cancelled, the new
* controller should not make any assumptions about the state of the layout or its children.
* Their translation, alpha, scale, etc. values may have been changed by the previous
* controller and should be reset here if relevant.
*/
abstract void onActiveControllerForLayout(PhysicsAnimationLayout layout);
protected PhysicsAnimationLayout mLayout; protected PhysicsAnimationLayout mLayout;
PhysicsAnimationController() { } PhysicsAnimationController() { }
/** Whether this controller is the currently active controller for its associated layout. */
protected boolean isActiveController() {
return this == mLayout.mController;
}
protected void setLayout(PhysicsAnimationLayout layout) { protected void setLayout(PhysicsAnimationLayout layout) {
this.mLayout = layout; this.mLayout = layout;
onActiveControllerForLayout(layout);
} }
protected PhysicsAnimationLayout getLayout() { protected PhysicsAnimationLayout getLayout() {
return mLayout; return mLayout;
} }
/**
* Sets the child's visibility when it moves beyond or within the limits set by a call to
* {@link PhysicsAnimationLayout#setMaxRenderedChildren}. This can be overridden to animate
* this transition.
*/
protected void setChildVisibility(View child, int index, int visibility) {
child.setVisibility(visibility);
}
/** /**
* Returns a {@link PhysicsPropertyAnimator} instance for the given child view. * Returns a {@link PhysicsPropertyAnimator} instance for the given child view.
*/ */
@@ -170,6 +184,9 @@ public class PhysicsAnimationLayout extends FrameLayout {
child.setTag(R.id.physics_animator_tag, animator); child.setTag(R.id.physics_animator_tag, animator);
} }
animator.clearAnimator();
animator.setAssociatedController(this);
return animator; return animator;
} }
@@ -235,32 +252,17 @@ public class PhysicsAnimationLayout extends FrameLayout {
new HashMap<>(); new HashMap<>();
/** The currently active animation controller. */ /** The currently active animation controller. */
private PhysicsAnimationController mController; @Nullable protected PhysicsAnimationController mController;
/**
* The maximum number of children to render and animate at a time. See
* {@link #setMaxRenderedChildren}.
*/
private int mMaxRenderedChildren = 5;
public PhysicsAnimationLayout(Context context) { public PhysicsAnimationLayout(Context context) {
super(context); super(context);
} }
/**
* The maximum number of children to render and animate at a time. Any child views added beyond
* this limit will be set to {@link View#GONE}. If any animations attempt to run on the view,
* the corresponding property will be set with no animation.
*/
public void setMaxRenderedChildren(int max) {
this.mMaxRenderedChildren = max;
}
/** /**
* Sets the animation controller and constructs or reconfigures the layout's physics animations * Sets the animation controller and constructs or reconfigures the layout's physics animations
* to meet the controller's specifications. * to meet the controller's specifications.
*/ */
public void setController(PhysicsAnimationController controller) { public void setActiveController(PhysicsAnimationController controller) {
cancelAllAnimations(); cancelAllAnimations();
mEndActionForProperty.clear(); mEndActionForProperty.clear();
@@ -312,42 +314,11 @@ public class PhysicsAnimationLayout extends FrameLayout {
@Override @Override
public void addView(View child, int index, ViewGroup.LayoutParams params) { public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params); addViewInternal(child, index, params, false /* isReorder */);
// Set up animations for the new view, if the controller is set. If it isn't set, we'll be
// setting up animations for all children when setController is called.
if (mController != null) {
for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
setUpAnimationForChild(property, child, index);
}
mController.onChildAdded(child, index);
}
setChildrenVisibility();
} }
@Override @Override
public void removeView(View view) { public void removeView(View view) {
removeViewAndThen(view, /* callback */ null);
}
@Override
public void removeViewAt(int index) {
removeView(getChildAt(index));
}
/** 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.
*/
public void removeViewAndThen(View view, Runnable callback) {
if (mController != null) { if (mController != null) {
final int index = indexOfChild(view); final int index = indexOfChild(view);
@@ -355,8 +326,6 @@ public class PhysicsAnimationLayout extends FrameLayout {
super.removeView(view); super.removeView(view);
addTransientView(view, index); addTransientView(view, index);
setChildrenVisibility();
// Tell the controller to animate this view out, and call the callback when it's // Tell the controller to animate this view out, and call the callback when it's
// finished. // finished.
mController.onChildRemoved(view, index, () -> { mController.onChildRemoved(view, index, () -> {
@@ -364,19 +333,28 @@ public class PhysicsAnimationLayout extends FrameLayout {
// any are still running and then remove it. // any are still running and then remove it.
cancelAnimationsOnView(view); cancelAnimationsOnView(view);
removeTransientView(view); removeTransientView(view);
if (callback != null) {
callback.run();
}
}); });
} else { } else {
// Without a controller, nobody will animate this view out, so it gets an unceremonious // Without a controller, nobody will animate this view out, so it gets an unceremonious
// departure. // departure.
super.removeView(view); super.removeView(view);
}
}
if (callback != null) { @Override
callback.run(); public void removeViewAt(int index) {
} removeView(getChildAt(index));
}
/** Immediately re-orders the view to the given index. */
public void reorderView(View view, int index) {
final int oldIndex = indexOfChild(view);
super.removeView(view);
addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */);
if (mController != null) {
mController.onChildReordered(view, oldIndex, index);
} }
} }
@@ -427,6 +405,10 @@ public class PhysicsAnimationLayout extends FrameLayout {
} }
} }
protected boolean isActiveController(PhysicsAnimationController controller) {
return mController == controller;
}
/** Whether the first child would be left of center if translated to the given x value. */ /** Whether the first child would be left of center if translated to the given x value. */
protected boolean isFirstChildXLeftOfCenter(float x) { protected boolean isFirstChildXLeftOfCenter(float x) {
if (getChildCount() > 0) { if (getChildCount() > 0) {
@@ -453,6 +435,26 @@ public class PhysicsAnimationLayout extends FrameLayout {
} }
} }
/**
* Adds a view to the layout. If this addition is not the result of a call to
* {@link #reorderView}, this will also notify the controller via
* {@link PhysicsAnimationController#onChildAdded} and set up animations for the view.
*/
private void addViewInternal(
View child, int index, ViewGroup.LayoutParams params, boolean isReorder) {
super.addView(child, index, params);
// Set up animations for the new view, if the controller is set. If it isn't set, we'll be
// setting up animations for all children when setActiveController is called.
if (mController != null && !isReorder) {
for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
setUpAnimationForChild(property, child, index);
}
mController.onChildAdded(child, index);
}
}
/** /**
* Retrieves the animation of the given property from the view at the given index via the view * Retrieves the animation of the given property from the view at the given index via the view
* tag system. * tag system.
@@ -481,33 +483,16 @@ public class PhysicsAnimationLayout extends FrameLayout {
SpringAnimation newAnim = new SpringAnimation(child, property); SpringAnimation newAnim = new SpringAnimation(child, property);
newAnim.addUpdateListener((animation, value, velocity) -> { newAnim.addUpdateListener((animation, value, velocity) -> {
final int indexOfChild = indexOfChild(child); final int indexOfChild = indexOfChild(child);
final int nextAnimInChain = final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild);
mController.getNextAnimationInChain(property, indexOfChild);
if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) { if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) {
return; return;
} }
final int animIndex = indexOfChild(child); final float offset = mController.getOffsetForChainedPropertyAnimation(property);
final float offset = if (nextAnimInChain < getChildCount()) {
mController.getOffsetForChainedPropertyAnimation(property); getAnimationAtIndex(property, nextAnimInChain)
// 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)) {
getAnimationAtIndex(property, animIndex + 1)
.animateToFinalPosition(value + offset); .animateToFinalPosition(value + offset);
} else if (nextAnimInChain < getChildCount()) {
// If the next child view is not rendered, update the property directly without
// animating it, so that the view is still in the correct state if it later
// becomes visible.
for (int i = nextAnimInChain; i < getChildCount(); i++) {
// 'value' here is the value of the last child within the rendering limit,
// not the first child's value - so we want to subtract the last child's
// index when calculating the offset.
property.setValue(getChildAt(i), value + offset * (i - animIndex));
}
} }
}); });
@@ -516,22 +501,6 @@ public class PhysicsAnimationLayout extends FrameLayout {
child.setTag(getTagIdForProperty(property), newAnim); child.setTag(getTagIdForProperty(property), newAnim);
} }
/** Hides children beyond the max rendering count. */
private void setChildrenVisibility() {
for (int i = 0; i < getChildCount(); i++) {
final int targetVisibility = i < mMaxRenderedChildren ? View.VISIBLE : View.GONE;
final View targetView = getChildAt(i);
if (targetView.getVisibility() != targetVisibility) {
if (mController != null) {
mController.setChildVisibility(targetView, i, targetVisibility);
} else {
targetView.setVisibility(targetVisibility);
}
}
}
}
/** Return a stable ID to use as a tag key for the given property's animations. */ /** Return a stable ID to use as a tag key for the given property's animations. */
private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { private int getTagIdForProperty(DynamicAnimation.ViewProperty property) {
if (property.equals(DynamicAnimation.TRANSLATION_X)) { if (property.equals(DynamicAnimation.TRANSLATION_X)) {
@@ -592,7 +561,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
private View mView; private View mView;
/** Start velocity to use for all property animations. */ /** Start velocity to use for all property animations. */
private float mDefaultStartVelocity = 0f; private float mDefaultStartVelocity = -Float.MAX_VALUE;
/** Start delay to use when start is called. */ /** Start delay to use when start is called. */
private long mStartDelay = 0; private long mStartDelay = 0;
@@ -625,6 +594,15 @@ public class PhysicsAnimationLayout extends FrameLayout {
*/ */
private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>(); private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>();
/**
* All of the initial property values that have been set. These values will be instantly set
* when {@link #start} is called, just before the animation begins.
*/
private Map<DynamicAnimation.ViewProperty, Float> mInitialPropertyValues = new HashMap<>();
/** The animation controller that last retrieved this animator instance. */
private PhysicsAnimationController mAssociatedController;
protected PhysicsPropertyAnimator(View view) { protected PhysicsPropertyAnimator(View view) {
this.mView = view; this.mView = view;
} }
@@ -644,7 +622,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Set the view's alpha value to 'from', then animate it to the given value. */ /** Set the view's alpha value to 'from', then animate it to the given value. */
public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) { public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) {
mView.setAlpha(from); mInitialPropertyValues.put(DynamicAnimation.ALPHA, from);
return alpha(to, endActions); return alpha(to, endActions);
} }
@@ -656,7 +634,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Set the view's translationX value to 'from', then animate it to the given value. */ /** Set the view's translationX value to 'from', then animate it to the given value. */
public PhysicsPropertyAnimator translationX( public PhysicsPropertyAnimator translationX(
float from, float to, Runnable... endActions) { float from, float to, Runnable... endActions) {
mView.setTranslationX(from); mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_X, from);
return translationX(to, endActions); return translationX(to, endActions);
} }
@@ -668,7 +646,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Set the view's translationY value to 'from', then animate it to the given value. */ /** Set the view's translationY value to 'from', then animate it to the given value. */
public PhysicsPropertyAnimator translationY( public PhysicsPropertyAnimator translationY(
float from, float to, Runnable... endActions) { float from, float to, Runnable... endActions) {
mView.setTranslationY(from); mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_Y, from);
return translationY(to, endActions); return translationY(to, endActions);
} }
@@ -690,7 +668,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Set the view's scaleX value to 'from', then animate it to the given value. */ /** Set the view's scaleX value to 'from', then animate it to the given value. */
public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) { public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) {
mView.setScaleX(from); mInitialPropertyValues.put(DynamicAnimation.SCALE_X, from);
return scaleX(to, endActions); return scaleX(to, endActions);
} }
@@ -701,7 +679,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Set the view's scaleY value to 'from', then animate it to the given value. */ /** Set the view's scaleY value to 'from', then animate it to the given value. */
public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) { public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) {
mView.setScaleY(from); mInitialPropertyValues.put(DynamicAnimation.SCALE_Y, from);
return scaleY(to, endActions); return scaleY(to, endActions);
} }
@@ -750,6 +728,13 @@ public class PhysicsAnimationLayout extends FrameLayout {
* animated property on every child (including chained animations) have ended. * animated property on every child (including chained animations) have ended.
*/ */
public void start(Runnable... after) { public void start(Runnable... after) {
if (!isActiveController(mAssociatedController)) {
Log.w(TAG, "Only the active animation controller is allowed to start animations. "
+ "Use PhysicsAnimationLayout#setActiveController to set the active "
+ "animation controller.");
return;
}
final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties(); final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties();
// If there are end actions, set an end listener on the layout for all the properties // If there are end actions, set an end listener on the layout for all the properties
@@ -791,6 +776,10 @@ public class PhysicsAnimationLayout extends FrameLayout {
// Actually start the animations. // Actually start the animations.
for (DynamicAnimation.ViewProperty property : properties) { for (DynamicAnimation.ViewProperty property : properties) {
if (mInitialPropertyValues.containsKey(property)) {
property.setValue(mView, mInitialPropertyValues.get(property));
}
final SpringForce defaultSpringForce = mController.getSpringForce(property, mView); final SpringForce defaultSpringForce = mController.getSpringForce(property, mView);
animateValueForChild( animateValueForChild(
property, property,
@@ -803,14 +792,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
mEndActionsForProperty.get(property)); mEndActionsForProperty.get(property));
} }
// Clear out the animator. clearAnimator();
mAnimatedProperties.clear();
mPositionStartVelocities.clear();
mDefaultStartVelocity = 0;
mStartDelay = 0;
mStiffness = -1;
mDampingRatio = -1;
mEndActionsForProperty.clear();
} }
/** Returns the set of properties that will animate once {@link #start} is called. */ /** Returns the set of properties that will animate once {@link #start} is called. */
@@ -847,20 +829,50 @@ public class PhysicsAnimationLayout extends FrameLayout {
}); });
} }
animation.getSpring().setStiffness(stiffness); final SpringForce animationSpring = animation.getSpring();
animation.getSpring().setDampingRatio(dampingRatio);
if (startVel > 0) { if (animationSpring == null) {
animation.setStartVelocity(startVel); return;
} }
final Runnable configureAndStartAnimation = () -> {
animationSpring.setStiffness(stiffness);
animationSpring.setDampingRatio(dampingRatio);
if (startVel > -Float.MAX_VALUE) {
animation.setStartVelocity(startVel);
}
animationSpring.setFinalPosition(value);
animation.start();
};
if (startDelay > 0) { if (startDelay > 0) {
postDelayed(() -> animation.animateToFinalPosition(value), startDelay); postDelayed(configureAndStartAnimation, startDelay);
} else { } else {
animation.animateToFinalPosition(value); configureAndStartAnimation.run();
} }
} }
} }
private void clearAnimator() {
mInitialPropertyValues.clear();
mAnimatedProperties.clear();
mPositionStartVelocities.clear();
mDefaultStartVelocity = -Float.MAX_VALUE;
mStartDelay = 0;
mStiffness = -1;
mDampingRatio = -1;
mEndActionsForProperty.clear();
}
/**
* Sets the controller that last retrieved this animator instance, so that we can prevent
* {@link #start} from actually starting animations if called by a non-active controller.
*/
private void setAssociatedController(PhysicsAnimationController controller) {
mAssociatedController = controller;
}
} }
@Override @Override

View File

@@ -154,21 +154,6 @@ public class StackAnimationController extends
/** Height of the status bar. */ /** Height of the status bar. */
private float mStatusBarHeight; private float mStatusBarHeight;
@Override
protected void setLayout(PhysicsAnimationLayout layout) {
super.setLayout(layout);
Resources res = layout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackStartingVerticalOffset =
res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}
/** /**
* Instantly move the first bubble to the given point, and animate the rest of the stack behind * Instantly move the first bubble to the given point, and animate the rest of the stack behind
* it with the 'following' effect. * it with the 'following' effect.
@@ -286,6 +271,8 @@ public class StackAnimationController extends
}, },
DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
// If we're flinging now, there's no more touch event to catch up to.
mFirstBubbleSpringingToTouch = false;
mIsMovingFromFlinging = true; mIsMovingFromFlinging = true;
return destinationRelativeX; return destinationRelativeX;
} }
@@ -656,19 +643,38 @@ public class StackAnimationController extends
if (mLayout.getChildCount() > 0) { if (mLayout.getChildCount() > 0) {
animationForChildAtIndex(0).translationX(mStackPosition.x).start(); animationForChildAtIndex(0).translationX(mStackPosition.x).start();
} else {
// Set the start position back to the default since we're out of bubbles. New bubbles
// will then animate in from the start position.
mStackPosition = getDefaultStartPosition();
} }
} }
@Override
void onChildReordered(View child, int oldIndex, int newIndex) {}
@Override
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
Resources res = layout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackStartingVerticalOffset =
res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}
/** Moves the stack, without any animation, to the starting position. */ /** Moves the stack, without any animation, to the starting position. */
private void moveStackToStartPosition() { private void moveStackToStartPosition() {
// Post to ensure that the layout's width and height have been calculated. // Post to ensure that the layout's width and height have been calculated.
mLayout.setVisibility(View.INVISIBLE); mLayout.setVisibility(View.INVISIBLE);
mLayout.post(() -> { mLayout.post(() -> {
setStackPosition(mRestingStackPosition == null
? getDefaultStartPosition()
: mRestingStackPosition);
mStackMovedToStartPosition = true; mStackMovedToStartPosition = true;
setStackPosition(
mRestingStackPosition == null
? getDefaultStartPosition()
: mRestingStackPosition);
mLayout.setVisibility(View.VISIBLE); mLayout.setVisibility(View.VISIBLE);
// Animate in the top bubble now that we're visible. // Animate in the top bubble now that we're visible.
@@ -707,15 +713,20 @@ public class StackAnimationController extends
Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y)); Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
mStackPosition.set(pos.x, pos.y); mStackPosition.set(pos.x, pos.y);
mLayout.cancelAllAnimations(); // If we're not the active controller, we don't want to physically move the bubble views.
cancelStackPositionAnimations(); if (isActiveController()) {
mLayout.cancelAllAnimations();
cancelStackPositionAnimations();
// Since we're not using the chained animations, apply the offsets manually. // Since we're not using the chained animations, apply the offsets manually.
final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); final float xOffset = getOffsetForChainedPropertyAnimation(
final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); DynamicAnimation.TRANSLATION_X);
for (int i = 0; i < mLayout.getChildCount(); i++) { final float yOffset = getOffsetForChainedPropertyAnimation(
mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset)); DynamicAnimation.TRANSLATION_Y);
mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset)); for (int i = 0; i < mLayout.getChildCount(); i++) {
mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset));
mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset));
}
} }
} }
@@ -732,6 +743,10 @@ public class StackAnimationController extends
/** Animates in the given bubble. */ /** Animates in the given bubble. */
private void animateInBubble(View child) { private void animateInBubble(View child) {
if (!isActiveController()) {
return;
}
child.setTranslationY(mStackPosition.y); child.setTranslationY(mStackPosition.y);
float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);

View File

@@ -60,8 +60,8 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mLayout.setController(mExpandedController); mLayout.setActiveController(mExpandedController);
Resources res = mLayout.getResources(); Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -73,14 +73,14 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
@Test @Test
public void testExpansionAndCollapse() throws InterruptedException { public void testExpansionAndCollapse() throws InterruptedException {
Runnable afterExpand = Mockito.mock(Runnable.class); Runnable afterExpand = Mockito.mock(Runnable.class);
mExpandedController.expandFromStack(mExpansionPoint, afterExpand); mExpandedController.expandFromStack(afterExpand);
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
testBubblesInCorrectExpandedPositions(); testBubblesInCorrectExpandedPositions();
verify(afterExpand).run(); verify(afterExpand).run();
Runnable afterCollapse = Mockito.mock(Runnable.class); Runnable afterCollapse = Mockito.mock(Runnable.class);
mExpandedController.collapseBackToStack(afterCollapse); mExpandedController.collapseBackToStack(mExpansionPoint, afterCollapse);
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
@@ -139,7 +139,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
assertEquals(500f, draggedBubble.getTranslationY(), 1f); assertEquals(500f, draggedBubble.getTranslationY(), 1f);
// Snap it back and make sure it made it back correctly. // Snap it back and make sure it made it back correctly.
mExpandedController.prepareForDismissalWithVelocity(draggedBubble, 0f, 0f);
mLayout.removeView(draggedBubble); mLayout.removeView(draggedBubble);
waitForLayoutMessageQueue(); waitForLayoutMessageQueue();
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -169,7 +168,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
// Dismiss the now-magneted bubble, verify that the callback was called. // Dismiss the now-magneted bubble, verify that the callback was called.
final Runnable afterDismiss = Mockito.mock(Runnable.class); final Runnable afterDismiss = Mockito.mock(Runnable.class);
mExpandedController.dismissDraggedOutBubble(afterDismiss); mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss);
waitForPropertyAnimations(DynamicAnimation.ALPHA); waitForPropertyAnimations(DynamicAnimation.ALPHA);
verify(after).run(); verify(after).run();
@@ -224,7 +223,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
/** Expand the stack and wait for animations to finish. */ /** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException { private void expand() throws InterruptedException {
mExpandedController.expandFromStack(mExpansionPoint, Mockito.mock(Runnable.class)); mExpandedController.expandFromStack(Mockito.mock(Runnable.class));
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
} }
@@ -236,26 +235,19 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
assertEquals(x + i * offsetMultiplier * mStackOffset, assertEquals(x + i * offsetMultiplier * mStackOffset,
mLayout.getChildAt(i).getTranslationX(), 2f); mLayout.getChildAt(i).getTranslationX(), 2f);
assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f); assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
if (i < mMaxRenderedBubbles) {
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
} }
} }
/** Check that children are in the correct positions for being expanded. */ /** Check that children are in the correct positions for being expanded. */
private void testBubblesInCorrectExpandedPositions() { private void testBubblesInCorrectExpandedPositions() {
// Check all the visible bubbles to see if they're in the right place. // Check all the visible bubbles to see if they're in the right place.
for (int i = 0; i < Math.min(mLayout.getChildCount(), mMaxRenderedBubbles); i++) { for (int i = 0; i < mLayout.getChildCount(); i++) {
assertEquals(getBubbleLeft(i), assertEquals(getBubbleLeft(i),
mLayout.getChildAt(i).getTranslationX(), mLayout.getChildAt(i).getTranslationX(),
2f); 2f);
assertEquals(mExpandedController.getExpandedY(), assertEquals(mExpandedController.getExpandedY(),
mLayout.getChildAt(i).getTranslationY(), 2f); mLayout.getChildAt(i).getTranslationY(), 2f);
if (i < mMaxRenderedBubbles) {
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
} }
} }
@@ -273,9 +265,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
return 0; return 0;
} }
int bubbleCount = mLayout.getChildCount(); int bubbleCount = mLayout.getChildCount();
if (bubbleCount > mMaxRenderedBubbles) {
bubbleCount = mMaxRenderedBubbles;
}
// Width calculations. // Width calculations.
double bubble = bubbleCount * mBubbleSize; double bubble = bubbleCount * mBubbleSize;
float gap = (bubbleCount - 1) * mBubblePadding; float gap = (bubbleCount - 1) * mBubblePadding;

View File

@@ -23,7 +23,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -76,22 +75,10 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
mTestableController.setRemoveImmediately(false); mTestableController.setRemoveImmediately(false);
} }
@Test
public void testRenderVisibility() throws InterruptedException {
mLayout.setController(mTestableController);
addOneMoreThanRenderLimitBubbles();
// The last child should be GONE, the rest VISIBLE.
for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE,
mLayout.getChildAt(i).getVisibility());
}
}
@Test @Test
public void testHierarchyChanges() throws InterruptedException { public void testHierarchyChanges() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
// Make sure the controller was notified of all the views we added. // Make sure the controller was notified of all the views we added.
for (View mView : mViews) { for (View mView : mViews) {
@@ -115,8 +102,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testUpdateValueNotChained() throws InterruptedException { public void testUpdateValueNotChained() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
// Don't chain any values. // Don't chain any values.
mTestableController.setChainedProperties(Sets.newHashSet()); mTestableController.setChainedProperties(Sets.newHashSet());
@@ -146,8 +133,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testSetEndActions() throws InterruptedException { public void testSetEndActions() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mTestableController.setChainedProperties(Sets.newHashSet()); mTestableController.setChainedProperties(Sets.newHashSet());
final CountDownLatch xLatch = new CountDownLatch(1); final CountDownLatch xLatch = new CountDownLatch(1);
@@ -189,8 +176,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testRemoveEndListeners() throws InterruptedException { public void testRemoveEndListeners() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mTestableController.setChainedProperties(Sets.newHashSet()); mTestableController.setChainedProperties(Sets.newHashSet());
final CountDownLatch xLatch = new CountDownLatch(1); final CountDownLatch xLatch = new CountDownLatch(1);
@@ -229,8 +216,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
public void testSetController() throws InterruptedException { public void testSetController() throws InterruptedException {
// Add the bubbles, then set the controller, to make sure that a controller added to an // Add the bubbles, then set the controller, to make sure that a controller added to an
// already-initialized view works correctly. // already-initialized view works correctly.
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
testChainedTranslationAnimations(); testChainedTranslationAnimations();
TestableAnimationController secondController = TestableAnimationController secondController =
@@ -243,7 +230,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
DynamicAnimation.SCALE_X, 10f); DynamicAnimation.SCALE_X, 10f);
secondController.setRemoveImmediately(true); secondController.setRemoveImmediately(true);
mLayout.setController(secondController); mLayout.setActiveController(secondController);
mTestableController.animationForChildAtIndex(0) mTestableController.animationForChildAtIndex(0)
.scaleX(1.5f) .scaleX(1.5f)
.start(); .start();
@@ -266,7 +253,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
Mockito.verify(secondController, Mockito.atLeastOnce()) Mockito.verify(secondController, Mockito.atLeastOnce())
.getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
mTestableController.animationForChildAtIndex(0) mTestableController.animationForChildAtIndex(0)
.translationX(100f) .translationX(100f)
.start(); .start();
@@ -283,8 +270,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testArePropertiesAnimating() throws InterruptedException { public void testArePropertiesAnimating() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
assertFalse(mLayout.arePropertiesAnimating( assertFalse(mLayout.arePropertiesAnimating(
DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
@@ -307,8 +294,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testCancelAllAnimations() throws InterruptedException { public void testCancelAllAnimations() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mTestableController.animationForChildAtIndex(0) mTestableController.animationForChildAtIndex(0)
.position(1000, 1000) .position(1000, 1000)
@@ -321,29 +308,10 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
assertTrue(mViews.get(0).getTranslationY() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000);
} }
@Test
public void testSetChildVisibility() throws InterruptedException {
mLayout.setController(mTestableController);
addOneMoreThanRenderLimitBubbles();
// The last view should have been set to GONE by the controller, since we added one more
// than the limit and it got pushed off. None of the first children should have been set
// VISIBLE, since they would have been animated in by onChildAdded.
Mockito.verify(mTestableController).setChildVisibility(
mViews.get(mViews.size() - 1), 5, View.GONE);
Mockito.verify(mTestableController, never()).setChildVisibility(
any(View.class), anyInt(), eq(View.VISIBLE));
// Remove the first view, which should cause the last view to become visible again.
mLayout.removeView(mViews.get(0));
Mockito.verify(mTestableController).setChildVisibility(
mViews.get(mViews.size() - 1), 4, View.VISIBLE);
}
/** Standard test of chained translation animations. */ /** Standard test of chained translation animations. */
private void testChainedTranslationAnimations() throws InterruptedException { private void testChainedTranslationAnimations() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
@@ -354,11 +322,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
// Since we enabled chaining, animating the first view to 100 should animate the second to for (int i = 0; i < mLayout.getChildCount(); i++) {
// 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble
// not being visible, or animated, make sure that it has the appropriate chained
// translation.
for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
assertEquals( assertEquals(
100 + i * TEST_TRANSLATION_X_OFFSET, 100 + i * TEST_TRANSLATION_X_OFFSET,
mLayout.getChildAt(i).getTranslationX(), .1f); mLayout.getChildAt(i).getTranslationX(), .1f);
@@ -383,8 +347,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testPhysicsAnimator() throws InterruptedException { public void testPhysicsAnimator() throws InterruptedException {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
Runnable afterAll = Mockito.mock(Runnable.class); Runnable afterAll = Mockito.mock(Runnable.class);
Runnable after = Mockito.spy(new Runnable() { Runnable after = Mockito.spy(new Runnable() {
@@ -430,9 +394,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
// Don't chain since we're going to invoke each animation independently. // Don't chain since we're going to invoke each animation independently.
mTestableController.setChainedProperties(new HashSet<>()); mTestableController.setChainedProperties(new HashSet<>());
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
Runnable allEnd = Mockito.mock(Runnable.class); Runnable allEnd = Mockito.mock(Runnable.class);
@@ -452,7 +416,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
@Test @Test
public void testAnimationsForChildrenFromIndex_noChildren() { public void testAnimationsForChildrenFromIndex_noChildren() {
mLayout.setController(mTestableController); mLayout.setActiveController(mTestableController);
final Runnable after = Mockito.mock(Runnable.class); final Runnable after = Mockito.mock(Runnable.class);
mTestableController mTestableController
@@ -523,8 +487,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
} }
@Override @Override
protected void setChildVisibility(View child, int index, int visibility) { void onChildReordered(View child, int oldIndex, int newIndex) {}
super.setChildVisibility(child, index, visibility);
} @Override
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {}
} }
} }

View File

@@ -56,7 +56,6 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
Handler mMainThreadHandler; Handler mMainThreadHandler;
int mMaxRenderedBubbles;
int mSystemWindowInsetSize = 50; int mSystemWindowInsetSize = 50;
int mCutoutInsetSize = 100; int mCutoutInsetSize = 100;
@@ -69,6 +68,8 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
@Mock @Mock
private DisplayCutout mCutout; private DisplayCutout mCutout;
private int mMaxBubbles;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
@@ -79,7 +80,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
mLayout.setTop(0); mLayout.setTop(0);
mLayout.setBottom(mHeight); mLayout.setBottom(mHeight);
mMaxRenderedBubbles = mMaxBubbles =
getContext().getResources().getInteger(R.integer.bubbles_max_rendered); getContext().getResources().getInteger(R.integer.bubbles_max_rendered);
mMainThreadHandler = new Handler(Looper.getMainLooper()); mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -96,8 +97,8 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
} }
/** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */
void addOneMoreThanRenderLimitBubbles() throws InterruptedException { void addOneMoreThanBubbleLimitBubbles() throws InterruptedException {
for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { for (int i = 0; i < mMaxBubbles + 1; i++) {
final View newView = new FrameLayout(mContext); final View newView = new FrameLayout(mContext);
mLayout.addView(newView, 0); mLayout.addView(newView, 0);
mViews.add(0, newView); mViews.add(0, newView);
@@ -137,6 +138,13 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
super(context); super(context);
} }
@Override
protected boolean isActiveController(PhysicsAnimationController controller) {
// Return true since otherwise all test controllers will be seen as inactive since they
// are wrapped by MainThreadAnimationControllerWrapper.
return true;
}
@Override @Override
public boolean post(Runnable action) { public boolean post(Runnable action) {
return mMainThreadHandler.post(action); return mMainThreadHandler.post(action);
@@ -148,9 +156,9 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
} }
@Override @Override
public void setController(PhysicsAnimationController controller) { public void setActiveController(PhysicsAnimationController controller) {
runOnMainThreadAndBlock( runOnMainThreadAndBlock(
() -> super.setController( () -> super.setActiveController(
new MainThreadAnimationControllerWrapper(controller))); new MainThreadAnimationControllerWrapper(controller)));
} }
@@ -267,8 +275,15 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
} }
@Override @Override
protected void setChildVisibility(View child, int index, int visibility) { void onChildReordered(View child, int oldIndex, int newIndex) {
mWrappedController.setChildVisibility(child, index, visibility); runOnMainThreadAndBlock(
() -> mWrappedController.onChildReordered(child, oldIndex, newIndex));
}
@Override
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
runOnMainThreadAndBlock(
() -> mWrappedController.onActiveControllerForLayout(layout));
} }
@Override @Override

View File

@@ -54,8 +54,8 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
mLayout.setController(mStackController); mLayout.setActiveController(mStackController);
addOneMoreThanRenderLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
} }