diff options
3 files changed, 238 insertions, 65 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 7f34ee0cdd3d..f794fef48f27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -23,6 +23,8 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Point; import android.util.Log; +import android.util.Size; +import android.view.View; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -33,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget; /** * Helper class to animate a {@link BubbleBarExpandedView} on a bubble. @@ -44,6 +47,13 @@ public class BubbleBarAnimationHelper { private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f; private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; + private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100; + /** + * Additional scale applied to expanded view when it is positioned inside a magnetic target. + */ + private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f; + private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300; + private static final int EXPANDED_VIEW_DISMISS_DURATION = 250; /** Spring config for the expanded view scale-in animation. */ private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = @@ -181,7 +191,8 @@ public class BubbleBarAnimationHelper { Log.w(TAG, "Trying to animate collapse without a bubble"); return; } - + bbev.setScaleX(1f); + bbev.setScaleY(1f); mExpandedViewContainerMatrix.setScaleX(1f); mExpandedViewContainerMatrix.setScaleY(1f); @@ -209,11 +220,124 @@ public class BubbleBarAnimationHelper { } /** + * Animates dismissal of currently expanded bubble + * + * @param endRunnable a runnable to run at the end of the animation + */ + public void animateDismiss(Runnable endRunnable) { + mIsExpanded = false; + final BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { + Log.w(TAG, "Trying to animate dismiss without a bubble"); + return; + } + + int[] location = bbev.getLocationOnScreen(); + int diffFromBottom = mPositioner.getScreenRect().bottom - location[1]; + + bbev.animate() + // 2x distance from bottom so the view flies out + .translationYBy(diffFromBottom * 2) + .setDuration(EXPANDED_VIEW_DISMISS_DURATION) + .withEndAction(endRunnable) + .start(); + } + + /** + * Animate current expanded bubble back to its rest position + */ + public void animateToRestPosition() { + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { + Log.w(TAG, "Trying to animate expanded view to rest position without a bubble"); + return; + } + Point restPoint = getExpandedViewRestPosition(getExpandedViewSize()); + bbev.animate() + .x(restPoint.x) + .y(restPoint.y) + .scaleX(1f) + .scaleY(1f) + .setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION) + .setInterpolator(Interpolators.EMPHASIZED_DECELERATE) + .withStartAction(() -> bbev.setAnimating(true)) + .withEndAction(() -> bbev.setAnimating(false)) + .start(); + } + + /** + * Animates currently expanded bubble into the given {@link MagneticTarget}. + * + * @param target magnetic target to snap to + * @param endRunnable a runnable to run at the end of the animation + */ + public void animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable) { + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { + Log.w(TAG, "Trying to snap the expanded view to target without a bubble"); + return; + } + Point expandedViewCenter = getViewCenterOnScreen(bbev); + + // Calculate the difference between the target's center coordinates and the object's. + // Animating the object's x/y properties by these values will center the object on top + // of the magnetic target. + float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x; + float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y; + + // Calculate scale of expanded view so it fits inside the magnetic target + float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight()); + float targetMaxSide = Math.max(target.getTargetView().getWidth(), + target.getTargetView().getHeight()); + float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide; + + bbev.animate() + .translationX(bbev.getTranslationX() + xDiff) + .translationY(bbev.getTranslationY() + yDiff) + .scaleX(scale) + .scaleY(scale) + .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION) + .setInterpolator(Interpolators.EMPHASIZED) + .withStartAction(() -> bbev.setAnimating(true)) + .withEndAction(() -> { + bbev.setAnimating(false); + if (endRunnable != null) { + endRunnable.run(); + } + }) + .start(); + } + + /** + * Animate currently expanded view when it is released from dismiss view + */ + public void animateUnstuckFromDismissView() { + BubbleBarExpandedView expandedView = getExpandedView(); + if (expandedView == null) { + Log.w(TAG, "Trying to unsnap the expanded view from dismiss without a bubble"); + return; + } + expandedView + .animate() + .scaleX(1f) + .scaleY(1f) + .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION) + .setInterpolator(Interpolators.EMPHASIZED) + .withStartAction(() -> expandedView.setAnimating(true)) + .withEndAction(() -> expandedView.setAnimating(false)) + .start(); + } + + /** * Cancel current animations */ public void cancelAnimations() { PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); mExpandedViewAlphaAnimator.cancel(); + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev != null) { + bbev.animate().cancel(); + } } private @Nullable BubbleBarExpandedView getExpandedView() { @@ -231,21 +355,42 @@ public class BubbleBarAnimationHelper { return; } - boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); - final int padding = mPositioner.getBubbleBarExpandedViewPadding(); - final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); + final Size size = getExpandedViewSize(); + Point position = getExpandedViewRestPosition(size); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); - lp.width = width; - lp.height = height; + lp.width = size.getWidth(); + lp.height = size.getHeight(); bbev.setLayoutParams(lp); + bbev.setX(position.x); + bbev.setY(position.y); + bbev.updateLocation(); + bbev.maybeShowOverflow(); + } + + private Point getExpandedViewRestPosition(Size size) { + final int padding = mPositioner.getBubbleBarExpandedViewPadding(); + Point point = new Point(); if (mLayerView.isOnLeft()) { - bbev.setX(mPositioner.getInsets().left + padding); + point.x = mPositioner.getInsets().left + padding; } else { - bbev.setX(mPositioner.getAvailableRect().width() - width - padding); + point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding; } - bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); - bbev.updateLocation(); - bbev.maybeShowOverflow(); + point.y = mPositioner.getExpandedViewBottomForBubbleBar() - size.getHeight(); + return point; + } + + private Size getExpandedViewSize() { + boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); + return new Size(width, height); + } + + private Point getViewCenterOnScreen(View view) { + Point center = new Point(); + int[] onScreenLocation = view.getLocationOnScreen(); + center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f)); + center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f)); + return center; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index 4ea18f78f5b2..d21545079cc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -16,70 +16,67 @@ package com.android.wm.shell.bubbles.bar -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.graphics.PointF -import android.graphics.Rect +import android.annotation.SuppressLint import android.view.MotionEvent import android.view.View -import com.android.wm.shell.animation.Interpolators import com.android.wm.shell.common.bubbles.DismissView import com.android.wm.shell.common.bubbles.RelativeTouchListener +import com.android.wm.shell.common.magnetictarget.MagnetizedObject /** Controller for handling drag interactions with [BubbleBarExpandedView] */ +@SuppressLint("ClickableViewAccessibility") class BubbleBarExpandedViewDragController( private val expandedView: BubbleBarExpandedView, private val dismissView: DismissView, + private val animationHelper: BubbleBarAnimationHelper, private val onDismissed: () -> Unit ) { + var isStuckToDismiss: Boolean = false + private set + + private var expandedViewInitialTranslationX = 0f + private var expandedViewInitialTranslationY = 0f + private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> = + MagnetizedObject.magnetizeView(expandedView) + private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget + init { - expandedView.handleView.setOnTouchListener(HandleDragListener()) - } + magnetizedExpandedView.magnetListener = MagnetListener() + magnetizedExpandedView.animateStuckToTarget = + { + target: MagnetizedObject.MagneticTarget, + _: Float, + _: Float, + _: Boolean, + after: (() -> Unit)? -> + animationHelper.animateIntoTarget(target, after) + } - private fun finishDrag(x: Float, y: Float, viewInitialX: Float, viewInitialY: Float) { - val dismissCircleBounds = Rect().apply { dismissView.circle.getBoundsOnScreen(this) } - if (dismissCircleBounds.contains(x.toInt(), y.toInt())) { - onDismissed() - } else { - resetExpandedViewPosition(viewInitialX, viewInitialY) - } - dismissView.hide() - } + magnetizedDismissTarget = + MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width) + magnetizedExpandedView.addTarget(magnetizedDismissTarget) - private fun resetExpandedViewPosition(initialX: Float, initialY: Float) { - val listener = - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator) { - expandedView.isAnimating = true - } + val dragMotionEventHandler = HandleDragListener() - override fun onAnimationEnd(animation: Animator) { - expandedView.isAnimating = false - } + expandedView.handleView.setOnTouchListener { view, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + expandedViewInitialTranslationX = expandedView.translationX + expandedViewInitialTranslationY = expandedView.translationY } - expandedView - .animate() - .translationX(initialX) - .translationY(initialY) - .setDuration(RESET_POSITION_ANIM_DURATION) - .setInterpolator(Interpolators.EMPHASIZED_DECELERATE) - .setListener(listener) - .start() + val magnetConsumed = magnetizedExpandedView.maybeConsumeMotionEvent(event) + // Move events can be consumed by the magnetized object + if (event.actionMasked == MotionEvent.ACTION_MOVE && magnetConsumed) { + return@setOnTouchListener true + } + return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed + } } private inner class HandleDragListener : RelativeTouchListener() { - - private val expandedViewRestPosition = PointF() - override fun onDown(v: View, ev: MotionEvent): Boolean { // While animating, don't allow new touch events - if (expandedView.isAnimating) { - return false - } - expandedViewRestPosition.x = expandedView.translationX - expandedViewRestPosition.y = expandedView.translationY - return true + return !expandedView.isAnimating } override fun onMove( @@ -90,8 +87,8 @@ class BubbleBarExpandedViewDragController( dx: Float, dy: Float ) { - expandedView.translationX = expandedViewRestPosition.x + dx - expandedView.translationY = expandedViewRestPosition.y + dy + expandedView.translationX = expandedViewInitialTranslationX + dx + expandedView.translationY = expandedViewInitialTranslationY + dy dismissView.show() } @@ -105,16 +102,40 @@ class BubbleBarExpandedViewDragController( velX: Float, velY: Float ) { - finishDrag(ev.rawX, ev.rawY, expandedViewRestPosition.x, expandedViewRestPosition.y) + finishDrag() } override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) { - resetExpandedViewPosition(expandedViewRestPosition.x, expandedViewRestPosition.y) - dismissView.hide() + finishDrag() + } + + private fun finishDrag() { + if (!isStuckToDismiss) { + animationHelper.animateToRestPosition() + dismissView.hide() + } } } - companion object { - const val RESET_POSITION_ANIM_DURATION = 300L + private inner class MagnetListener : MagnetizedObject.MagnetListener { + override fun onStuckToTarget(target: MagnetizedObject.MagneticTarget) { + isStuckToDismiss = true + } + + override fun onUnstuckFromTarget( + target: MagnetizedObject.MagneticTarget, + velX: Float, + velY: Float, + wasFlungOut: Boolean + ) { + isStuckToDismiss = false + animationHelper.animateUnstuckFromDismissView() + } + + override fun onReleasedInTarget(target: MagnetizedObject.MagneticTarget) { + onDismissed() + dismissView.hide() + } } } + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index bdb0e206e490..12114519d086 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.bar; import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; +import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE; import android.annotation.Nullable; import android.content.Context; @@ -36,7 +37,6 @@ import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; -import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.bubbles.DeviceConfig; import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.bubbles.DismissView; @@ -206,10 +206,13 @@ public class BubbleBarLayerView extends FrameLayout } }); - mDragController = new BubbleBarExpandedViewDragController(mExpandedView, mDismissView, + mDragController = new BubbleBarExpandedViewDragController( + mExpandedView, + mDismissView, + mAnimationHelper, () -> { mBubbleController.dismissBubble(mExpandedBubble.getKey(), - Bubbles.DISMISS_USER_GESTURE); + DISMISS_USER_GESTURE); return Unit.INSTANCE; }); @@ -241,7 +244,11 @@ public class BubbleBarLayerView extends FrameLayout mIsExpanded = false; final BubbleBarExpandedView viewToRemove = mExpandedView; mEducationViewController.hideEducation(/* animated = */ true); - mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); + if (mDragController != null && mDragController.isStuckToDismiss()) { + mAnimationHelper.animateDismiss(() -> removeView(viewToRemove)); + } else { + mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); + } mBubbleController.getSysuiProxy().onStackExpandChanged(false); mExpandedView = null; mDragController = null; |