Merge "Animate the expanded view into the dismiss target" into main

This commit is contained in:
Ats Jenk 2023-12-15 20:57:42 +00:00 committed by Android (Google) Code Review
commit 5d7c7eaceb
3 changed files with 242 additions and 69 deletions

View File

@ -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);
@ -208,12 +219,125 @@ public class BubbleBarAnimationHelper {
mExpandedViewAlphaAnimator.reverse();
}
/**
* 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);
if (mLayerView.isOnLeft()) {
bbev.setX(mPositioner.getInsets().left + padding);
} else {
bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
}
bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
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()) {
point.x = mPositioner.getInsets().left + padding;
} else {
point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
}
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;
}
}

View File

@ -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())
}
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()
}
private fun resetExpandedViewPosition(initialX: Float, initialY: Float) {
val listener =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
expandedView.isAnimating = true
}
override fun onAnimationEnd(animation: Animator) {
expandedView.isAnimating = false
}
magnetizedExpandedView.magnetListener = MagnetListener()
magnetizedExpandedView.animateStuckToTarget =
{
target: MagnetizedObject.MagneticTarget,
_: Float,
_: Float,
_: Boolean,
after: (() -> Unit)? ->
animationHelper.animateIntoTarget(target, after)
}
expandedView
.animate()
.translationX(initialX)
.translationY(initialY)
.setDuration(RESET_POSITION_ANIM_DURATION)
.setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
.setListener(listener)
.start()
magnetizedDismissTarget =
MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width)
magnetizedExpandedView.addTarget(magnetizedDismissTarget)
val dragMotionEventHandler = HandleDragListener()
expandedView.handleView.setOnTouchListener { view, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
expandedViewInitialTranslationX = expandedView.translationX
expandedViewInitialTranslationY = expandedView.translationY
}
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()
}
}
}

View File

@ -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;