diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index d0be5ecb8f..c6803dc61a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -18,6 +18,11 @@ package com.android.launcher3.taskbar.bubbles; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; +import static com.android.launcher3.taskbar.bubbles.BubbleView.STASH_TRANSLATION_Y; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; @@ -34,6 +39,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.anim.AnimatedFloat; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.taskbar.TaskbarInsetsController; @@ -839,6 +845,53 @@ public class BubbleBarViewController { mBoundsChangeListener = listener; } + /** + * Create an animator for showing or hiding bubbles when stashed state changes + * + * @param isStashed {@code true} when bubble bar should be stashed to the handle + */ + public Animator createRevealAnimatorForStashChange(boolean isStashed) { + Rect stashedHandleBounds = new Rect(); + mBubbleStashController.getHandleBounds(stashedHandleBounds); + int childCount = mBarView.getChildCount(); + float newChildWidth = (float) stashedHandleBounds.width() / childCount; + float stashTranslationY = -mBubbleStashController.getBubbleBarTranslationY(); + AnimatorSet animatorSet = new AnimatorSet(); + for (int i = 0; i < childCount; i++) { + BubbleView child = (BubbleView) mBarView.getChildAt(i); + final float startTransY = isStashed ? 0f : stashTranslationY; + final float endTransY = isStashed ? stashTranslationY : 0f; + animatorSet.play( + ObjectAnimator.ofFloat(child, STASH_TRANSLATION_Y, startTransY, endTransY)); + animatorSet.play( + createRevealAnimForBubble(child, isStashed, stashedHandleBounds, + newChildWidth)); + } + return animatorSet; + } + + private Animator createRevealAnimForBubble(BubbleView bubbleView, boolean isStashed, + Rect stashedHandleBounds, float newWidth) { + Rect viewBounds = new Rect(0, 0, bubbleView.getWidth(), bubbleView.getHeight()); + + int viewCenterY = viewBounds.centerY(); + int halfHandleHeight = stashedHandleBounds.height() / 2; + int widthDelta = Math.max(0, (int) (viewBounds.width() - newWidth) / 2); + + Rect stashedViewBounds = new Rect( + viewBounds.left + widthDelta, + viewCenterY - halfHandleHeight, + viewBounds.right - widthDelta, + viewCenterY + halfHandleHeight + ); + + float viewRadius = 0f; // Use 0 to not clip the new message dot or the app icon + float stashedRadius = stashedViewBounds.height() / 2f; + + return new RoundedRectRevealOutlineProvider(viewRadius, stashedRadius, viewBounds, + stashedViewBounds).createRevealAnimator(bubbleView, !isStashed, 0); + } + /** * Listener to receive updates about bubble bar bounds changing */ diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java index fdd385a796..3640c3b60e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java @@ -186,6 +186,13 @@ public class BubbleStashedHandleViewController { return mStashedHandleHeight; } + /** + * Returns bounds of the stashed handle view + */ + public void getBounds(Rect bounds) { + bounds.set(mStashedHandleBounds); + } + /** * Called when system ui state changes. Bubbles don't show when the device is locked. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java index 591a9da5df..cc6b49a5c7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java @@ -20,12 +20,14 @@ import android.app.Notification; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.FloatProperty; import android.view.LayoutInflater; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; @@ -48,12 +50,27 @@ public class BubbleView extends ConstraintLayout { public static final int DEFAULT_PATH_SIZE = 100; + public static FloatProperty STASH_TRANSLATION_Y = new FloatProperty<>( + "stashTranslationY") { + @Override + public void setValue(BubbleView bubbleView, float transY) { + bubbleView.setStashTranslationY(transY); + } + + @Override + public Float get(BubbleView bubbleView) { + return bubbleView.mStashTranslationY; + } + }; + private final ImageView mBubbleIcon; private final ImageView mAppIcon; private int mBubbleSize; private float mDragTranslationX; private float mOffsetX; + private float mTranslationY; + private float mStashTranslationY; private DotRenderer mDotRenderer; private DotRenderer.DrawParams mDrawParams; @@ -110,6 +127,10 @@ public class BubbleView extends ConstraintLayout { setFocusable(true); setClickable(true); + + // We manage the shadow ourselves when creating the bitmap + setOutlineAmbientShadowColor(Color.TRANSPARENT); + setOutlineSpotShadowColor(Color.TRANSPARENT); } private void updateBubbleSizeAndDotRender() { @@ -152,16 +173,34 @@ public class BubbleView extends ConstraintLayout { applyDragTranslation(); } + private void applyDragTranslation() { + setTranslationX(mDragTranslationX + mOffsetX); + } + + /** + * Set translation in y direction during stash and unstash from handle + */ + public void setStashTranslationY(float translationY) { + mStashTranslationY = translationY; + applyTranslationY(); + } + + @Override + public void setTranslationY(float translationY) { + mTranslationY = translationY; + applyTranslationY(); + } + + private void applyTranslationY() { + super.setTranslationY(mTranslationY + mStashTranslationY); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); updateBubbleSizeAndDotRender(); } - private void applyDragTranslation() { - setTranslationX(mDragTranslationX + mOffsetX); - } - @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt index 8d632173a0..9721792540 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar.bubbles.stashing +import android.graphics.Rect import android.view.InsetsController import android.view.MotionEvent import android.view.View @@ -146,6 +147,9 @@ interface BubbleStashController { /** Returns the translation of the handle. */ fun getHandleTranslationY(): Float? + /** Returns bounds of the handle */ + fun getHandleBounds(bounds: Rect) + /** * Returns bubble bar Y position according to [isBubblesShowingOnHome] and * [isBubblesShowingOnOverview] values. Default implementation only analyse diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt index eaf4bf9fc7..7d6f7adbbf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt @@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.bubbles.stashing import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet +import android.graphics.Rect import android.view.MotionEvent import android.view.View import com.android.launcher3.anim.AnimatedFloat @@ -200,6 +201,10 @@ class PersistentBubbleStashController( override fun getHandleTranslationY(): Float? = null + override fun getHandleBounds(bounds: Rect) { + // no op since does not have a handle view + } + private fun updateExpandedState(expand: Boolean) { if (bubbleBarViewController.isHiddenForNoBubbles) { // If there are no bubbles the bar is invisible, nothing to do here. diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt index 0700ab7eb5..5f7c79c42b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt @@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.bubbles.stashing import android.animation.Animator import android.animation.AnimatorSet import android.content.Context +import android.graphics.Rect import android.view.MotionEvent import android.view.View import androidx.annotation.VisibleForTesting @@ -258,6 +259,10 @@ class TransientBubbleStashController( override fun getHandleTranslationY(): Float? = bubbleStashedHandleViewController?.translationY + override fun getHandleBounds(bounds: Rect) { + bubbleStashedHandleViewController?.getBounds(bounds) + } + private fun getStashTranslation(): Float { return (bubbleBarTranslationY - stashedHeight) / 2f } @@ -302,6 +307,13 @@ class TransientBubbleStashController( } ) + animatorSet.play( + bubbleBarViewController.createRevealAnimatorForStashChange(isStashed).apply { + this.duration = duration + this.interpolator = EMPHASIZED + } + ) + animatorSet.play( bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.apply { this.duration = duration diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt index a745b666db..20f1ff8e31 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar.bubbles.stashing +import android.animation.AnimatorSet import android.animation.AnimatorTestRule import android.content.Context import android.view.View @@ -307,6 +308,8 @@ class TransientBubbleStashControllerTest { whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha) whenever(bubbleBarViewController.bubbleBarCollapsedWidth).thenReturn(BUBBLE_BAR_WIDTH) whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT) + whenever(bubbleBarViewController.createRevealAnimatorForStashChange(any())) + .thenReturn(AnimatorSet()) } private fun setUpBubbleStashedHandleViewController() {