Merge changes I90625ae0,I96aaa686,I0f6584e1,I75219370,I68ed3bee
* changes: Modifies the PipMotionHelper to use PhysicsAnimator for PIP motion. Fix issue with left-flings when distanceToDestination = 0. Add FloatProperties, which contains helpful properties for animating Rects. Filter end actions for null, and allow Runnables from Java Cancel flings before restarting them.
This commit is contained in:
@@ -19,17 +19,11 @@ package com.android.systemui.pip.phone;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
|
||||
import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
|
||||
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
|
||||
|
||||
import android.animation.AnimationHandler;
|
||||
import android.animation.Animator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.RectEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
import android.animation.TimeAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager.StackInfo;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.IActivityTaskManager;
|
||||
@@ -42,13 +36,16 @@ import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.systemui.pip.PipSnapAlgorithm;
|
||||
import com.android.systemui.shared.system.WindowManagerWrapper;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
import com.android.systemui.util.animation.FloatProperties;
|
||||
import com.android.systemui.util.animation.PhysicsAnimator;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -60,18 +57,14 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
|
||||
private static final String TAG = "PipMotionHelper";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
|
||||
|
||||
private static final int DEFAULT_MOVE_STACK_DURATION = 225;
|
||||
private static final int SNAP_STACK_DURATION = 225;
|
||||
private static final int DRAG_TO_TARGET_DISMISS_STACK_DURATION = 375;
|
||||
private static final int DRAG_TO_DISMISS_STACK_DURATION = 175;
|
||||
private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
|
||||
private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
|
||||
private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300;
|
||||
private static final int MINIMIZE_STACK_MAX_DURATION = 200;
|
||||
private static final int SHIFT_DURATION = 300;
|
||||
|
||||
/** Friction to use for PIP when it moves via physics fling animations. */
|
||||
private static final float DEFAULT_FRICTION = 2f;
|
||||
|
||||
// The fraction of the stack width that the user has to drag offscreen to minimize the PiP
|
||||
private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f;
|
||||
// The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
|
||||
@@ -89,12 +82,39 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
|
||||
private PipMenuActivityController mMenuController;
|
||||
private PipSnapAlgorithm mSnapAlgorithm;
|
||||
private FlingAnimationUtils mFlingAnimationUtils;
|
||||
private AnimationHandler mAnimationHandler;
|
||||
|
||||
private final Rect mBounds = new Rect();
|
||||
private final Rect mStableInsets = new Rect();
|
||||
|
||||
private ValueAnimator mBoundsAnimator = null;
|
||||
/** PIP's current bounds on the screen. */
|
||||
private final Rect mBounds = new Rect();
|
||||
|
||||
/**
|
||||
* Bounds that are animated using the physics animator. PIP is moved to these bounds whenever
|
||||
* the {@link #mVsyncTimeAnimator} ticks.
|
||||
*/
|
||||
private final Rect mAnimatedBounds = new Rect();
|
||||
|
||||
/**
|
||||
* PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations.
|
||||
*/
|
||||
private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
|
||||
mAnimatedBounds);
|
||||
|
||||
/**
|
||||
* Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each
|
||||
* frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using
|
||||
* physics animations.
|
||||
*/
|
||||
private TimeAnimator mVsyncTimeAnimator;
|
||||
|
||||
/** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
|
||||
private PhysicsAnimator.FlingConfig mFlingConfigX;
|
||||
private PhysicsAnimator.FlingConfig mFlingConfigY;
|
||||
|
||||
/** SpringConfig to use for fling-then-spring animations. */
|
||||
private final PhysicsAnimator.SpringConfig mSpringConfig =
|
||||
new PhysicsAnimator.SpringConfig(
|
||||
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
|
||||
|
||||
public PipMotionHelper(Context context, IActivityManager activityManager,
|
||||
IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
|
||||
@@ -106,9 +126,39 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
|
||||
mMenuController = menuController;
|
||||
mSnapAlgorithm = snapAlgorithm;
|
||||
mFlingAnimationUtils = flingAnimationUtils;
|
||||
mAnimationHandler = new AnimationHandler();
|
||||
mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider());
|
||||
final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler();
|
||||
vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider());
|
||||
|
||||
onConfigurationChanged();
|
||||
|
||||
// Construct a time animator that uses the vsync frame provider. Physics animations can't
|
||||
// use custom frame providers, since they rely on constant time between frames to run the
|
||||
// physics simulations. To work around this, we physically-animate a second set of bounds,
|
||||
// and apply those animating bounds to the PIP in-sync via this TimeAnimator.
|
||||
mVsyncTimeAnimator = new TimeAnimator() {
|
||||
@Override
|
||||
public AnimationHandler getAnimationHandler() {
|
||||
return vsyncFrameCallbackProvider;
|
||||
}
|
||||
};
|
||||
|
||||
// When the time animator ticks, move PIP to the animated bounds.
|
||||
mVsyncTimeAnimator.setTimeListener(
|
||||
(animation, totalTime, deltaTime) ->
|
||||
resizePipUnchecked(mAnimatedBounds));
|
||||
|
||||
// Add a listener for cancel/end events that moves PIP to the final animated bounds.
|
||||
mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
resizePipUnchecked(mAnimatedBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
resizePipUnchecked(mAnimatedBounds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,89 +290,86 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flings the minimized PiP to the closest minimized snap target.
|
||||
*/
|
||||
Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) {
|
||||
cancelAnimations();
|
||||
// We currently only allow flinging the minimized stack up and down, so just lock the
|
||||
// movement bounds to the current stack bounds horizontally
|
||||
movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
|
||||
movementBounds.bottom);
|
||||
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
|
||||
0 /* velocityX */, velocityY, dragStartPosition);
|
||||
if (!mBounds.equals(toBounds)) {
|
||||
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
|
||||
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
|
||||
distanceBetweenRectOffsets(mBounds, toBounds),
|
||||
velocityY);
|
||||
mBoundsAnimator.start();
|
||||
}
|
||||
return toBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the PiP to the minimized state, slightly offscreen.
|
||||
*/
|
||||
Rect animateToClosestMinimizedState(Rect movementBounds,
|
||||
AnimatorUpdateListener updateListener) {
|
||||
cancelAnimations();
|
||||
Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
|
||||
if (!mBounds.equals(toBounds)) {
|
||||
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds,
|
||||
MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN);
|
||||
if (updateListener != null) {
|
||||
mBoundsAnimator.addUpdateListener(updateListener);
|
||||
}
|
||||
mBoundsAnimator.start();
|
||||
void animateToClosestMinimizedState(Rect movementBounds, @Nullable Runnable updateAction) {
|
||||
final Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
|
||||
|
||||
prepareForBoundsAnimation(movementBounds);
|
||||
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
|
||||
|
||||
if (updateAction != null) {
|
||||
mAnimatedBoundsPhysicsAnimator.addUpdateListener(
|
||||
(target, values) -> updateAction.run());
|
||||
}
|
||||
return toBounds;
|
||||
|
||||
startBoundsAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flings the PiP to the closest snap target.
|
||||
*/
|
||||
Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
|
||||
AnimatorUpdateListener updateListener, AnimatorListener listener,
|
||||
Point startPosition) {
|
||||
cancelAnimations();
|
||||
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
|
||||
velocityX, velocityY, startPosition);
|
||||
if (!mBounds.equals(toBounds)) {
|
||||
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
|
||||
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
|
||||
distanceBetweenRectOffsets(mBounds, toBounds),
|
||||
velocity);
|
||||
if (updateListener != null) {
|
||||
mBoundsAnimator.addUpdateListener(updateListener);
|
||||
}
|
||||
if (listener != null){
|
||||
mBoundsAnimator.addListener(listener);
|
||||
}
|
||||
mBoundsAnimator.start();
|
||||
}
|
||||
return toBounds;
|
||||
void flingToSnapTarget(
|
||||
float velocityX, float velocityY, Rect movementBounds, Runnable updateAction,
|
||||
@Nullable Runnable endAction) {
|
||||
prepareForBoundsAnimation(movementBounds);
|
||||
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.flingThenSpring(
|
||||
FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig,
|
||||
true /* flingMustReachMinOrMax */)
|
||||
.flingThenSpring(
|
||||
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
|
||||
.addUpdateListener((target, values) -> updateAction.run())
|
||||
.withEndActions(endAction);
|
||||
|
||||
startBoundsAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the PiP to the closest snap target.
|
||||
*/
|
||||
Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener,
|
||||
AnimatorListener listener) {
|
||||
cancelAnimations();
|
||||
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
|
||||
if (!mBounds.equals(toBounds)) {
|
||||
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
|
||||
FAST_OUT_SLOW_IN);
|
||||
if (updateListener != null) {
|
||||
mBoundsAnimator.addUpdateListener(updateListener);
|
||||
}
|
||||
if (listener != null){
|
||||
mBoundsAnimator.addListener(listener);
|
||||
}
|
||||
mBoundsAnimator.start();
|
||||
void animateToClosestSnapTarget(Rect movementBounds) {
|
||||
prepareForBoundsAnimation(movementBounds);
|
||||
|
||||
final Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
|
||||
|
||||
startBoundsAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dismissal of the PiP off the edge of the screen.
|
||||
*/
|
||||
void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) {
|
||||
final float velocity = PointF.length(velocityX, velocityY);
|
||||
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
|
||||
final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling);
|
||||
|
||||
// Set the animated bounds to start at the current bounds. We don't need to rebuild the
|
||||
// fling configs here via prepareForBoundsAnimation, since animateDismiss isn't provided
|
||||
// with new movement bounds.
|
||||
mAnimatedBounds.set(mBounds);
|
||||
|
||||
// Animate to the dismiss end point, and then dismiss PIP.
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig)
|
||||
.withEndActions(this::dismissPip);
|
||||
|
||||
// If we were provided with an update action, run it whenever there's an update.
|
||||
if (updateAction != null) {
|
||||
mAnimatedBoundsPhysicsAnimator.addUpdateListener(
|
||||
(target, values) -> updateAction.run());
|
||||
}
|
||||
return toBounds;
|
||||
|
||||
startBoundsAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,65 +424,43 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dismissal of the PiP off the edge of the screen.
|
||||
*/
|
||||
Rect animateDismiss(Rect pipBounds, float velocityX, float velocityY,
|
||||
AnimatorUpdateListener listener) {
|
||||
cancelAnimations();
|
||||
final float velocity = PointF.length(velocityX, velocityY);
|
||||
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
|
||||
Point p = getDismissEndPoint(pipBounds, velocityX, velocityY, isFling);
|
||||
Rect toBounds = new Rect(pipBounds);
|
||||
toBounds.offsetTo(p.x, p.y);
|
||||
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DRAG_TO_DISMISS_STACK_DURATION,
|
||||
FAST_OUT_LINEAR_IN);
|
||||
mBoundsAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
dismissPip();
|
||||
}
|
||||
});
|
||||
if (isFling) {
|
||||
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
|
||||
distanceBetweenRectOffsets(mBounds, toBounds), velocity);
|
||||
}
|
||||
if (listener != null) {
|
||||
mBoundsAnimator.addUpdateListener(listener);
|
||||
}
|
||||
mBoundsAnimator.start();
|
||||
return toBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all existing animations.
|
||||
*/
|
||||
void cancelAnimations() {
|
||||
if (mBoundsAnimator != null) {
|
||||
mBoundsAnimator.cancel();
|
||||
mBoundsAnimator = null;
|
||||
}
|
||||
private void cancelAnimations() {
|
||||
mAnimatedBoundsPhysicsAnimator.cancel();
|
||||
mVsyncTimeAnimator.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation to move the PiP to give given {@param toBounds}.
|
||||
* Set new fling configs whose min/max values respect the given movement bounds, and set the
|
||||
* animated bounds to PIP's current 'real' bounds.
|
||||
*/
|
||||
private ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration,
|
||||
Interpolator interpolator) {
|
||||
ValueAnimator anim = new ValueAnimator() {
|
||||
@Override
|
||||
public AnimationHandler getAnimationHandler() {
|
||||
return mAnimationHandler;
|
||||
}
|
||||
};
|
||||
anim.setObjectValues(fromBounds, toBounds);
|
||||
anim.setEvaluator(RECT_EVALUATOR);
|
||||
anim.setDuration(duration);
|
||||
anim.setInterpolator(interpolator);
|
||||
anim.addUpdateListener((ValueAnimator animation) -> {
|
||||
resizePipUnchecked((Rect) animation.getAnimatedValue());
|
||||
});
|
||||
return anim;
|
||||
private void prepareForBoundsAnimation(Rect movementBounds) {
|
||||
mFlingConfigX = new PhysicsAnimator.FlingConfig(
|
||||
DEFAULT_FRICTION, movementBounds.left, movementBounds.right);
|
||||
mFlingConfigY = new PhysicsAnimator.FlingConfig(
|
||||
DEFAULT_FRICTION, movementBounds.top, movementBounds.bottom);
|
||||
|
||||
mAnimatedBounds.set(mBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the physics animator which will update the animated PIP bounds using physics
|
||||
* animations, as well as the TimeAnimator which will apply those bounds to PIP at intervals
|
||||
* synchronized with the SurfaceFlinger vsync frame provider.
|
||||
*
|
||||
* This will also add end actions to the bounds animator that cancel the TimeAnimator and update
|
||||
* the 'real' bounds to equal the final animated bounds.
|
||||
*/
|
||||
private void startBoundsAnimation() {
|
||||
cancelAnimations();
|
||||
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.withEndActions(
|
||||
mVsyncTimeAnimator::cancel)
|
||||
.start();
|
||||
mVsyncTimeAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,10 +20,6 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STAT
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.IActivityTaskManager;
|
||||
import android.content.ComponentName;
|
||||
@@ -111,13 +107,6 @@ public class PipTouchHandler {
|
||||
}
|
||||
}
|
||||
};
|
||||
private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener =
|
||||
new AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
updateDismissFraction();
|
||||
}
|
||||
};
|
||||
|
||||
// Behaviour states
|
||||
private int mMenuState = MENU_STATE_NONE;
|
||||
@@ -162,7 +151,7 @@ public class PipTouchHandler {
|
||||
@Override
|
||||
public void onPipMinimize() {
|
||||
setMinimizedStateInternal(true);
|
||||
mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */);
|
||||
mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateAction */);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -655,15 +644,6 @@ public class PipTouchHandler {
|
||||
float lastY = mStartPosition.y + mDelta.y;
|
||||
float left = lastX + lastDelta.x;
|
||||
float top = lastY + lastDelta.y;
|
||||
if (!touchState.allowDraggingOffscreen() || !ENABLE_MINIMIZE) {
|
||||
left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
|
||||
}
|
||||
if (mEnableDimissDragToEdge) {
|
||||
// Allow pip to move past bottom bounds
|
||||
top = Math.max(mMovementBounds.top, top);
|
||||
} else {
|
||||
top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top));
|
||||
}
|
||||
|
||||
// Add to the cumulative delta after bounding the position
|
||||
mDelta.x += left - lastX;
|
||||
@@ -720,8 +700,9 @@ public class PipTouchHandler {
|
||||
if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
|
||||
MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
|
||||
PipUtils.getTopPinnedActivity(mContext, mActivityManager));
|
||||
mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x,
|
||||
vel.y, mUpdateScrimListener);
|
||||
mMotionHelper.animateDismiss(
|
||||
vel.x, vel.y,
|
||||
PipTouchHandler.this::updateDismissFraction /* updateAction */);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -739,8 +720,9 @@ public class PipTouchHandler {
|
||||
// minimize offset adjusted
|
||||
mMenuController.hideMenu();
|
||||
} else {
|
||||
mMotionHelper.animateToClosestMinimizedState(mMovementBounds,
|
||||
mUpdateScrimListener);
|
||||
mMotionHelper.animateToClosestMinimizedState(
|
||||
mMovementBounds,
|
||||
PipTouchHandler.this::updateDismissFraction /* updateAction */);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -750,7 +732,7 @@ public class PipTouchHandler {
|
||||
setMinimizedStateInternal(false);
|
||||
}
|
||||
|
||||
AnimatorListenerAdapter postAnimationCallback = null;
|
||||
Runnable endAction = null;
|
||||
if (mMenuState != MENU_STATE_NONE) {
|
||||
// If the menu is still visible, and we aren't minimized, then just poke the
|
||||
// menu so that it will timeout after the user stops touching it
|
||||
@@ -759,26 +741,20 @@ public class PipTouchHandler {
|
||||
} else {
|
||||
// If the menu is not visible, then we can still be showing the activity for the
|
||||
// dismiss overlay, so just finish it after the animation completes
|
||||
postAnimationCallback = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mMenuController.hideMenu();
|
||||
}
|
||||
};
|
||||
endAction = mMenuController::hideMenu;
|
||||
}
|
||||
|
||||
if (isFling) {
|
||||
mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
|
||||
mUpdateScrimListener, postAnimationCallback,
|
||||
mStartPosition);
|
||||
mMotionHelper.flingToSnapTarget(
|
||||
vel.x, vel.y, mMovementBounds,
|
||||
PipTouchHandler.this::updateDismissFraction /* updateAction */,
|
||||
endAction /* endAction */);
|
||||
} else {
|
||||
mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
|
||||
postAnimationCallback);
|
||||
mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
|
||||
}
|
||||
} else if (mIsMinimized) {
|
||||
// This was a tap, so no longer minimized
|
||||
mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
|
||||
null /* animatorListener */);
|
||||
mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
|
||||
setMinimizedStateInternal(false);
|
||||
} else if (mTouchState.isDoubleTap()) {
|
||||
// Expand to fullscreen if this is a double tap
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.util.animation
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat
|
||||
|
||||
/**
|
||||
* Helpful extra properties to use with the [PhysicsAnimator]. These allow you to animate objects
|
||||
* such as [Rect] and [RectF].
|
||||
*
|
||||
* There are additional, more basic properties available in [DynamicAnimation].
|
||||
*/
|
||||
class FloatProperties {
|
||||
companion object {
|
||||
/**
|
||||
* Represents the x-coordinate of a [Rect]. Typically used to animate moving a Rect
|
||||
* horizontally.
|
||||
*
|
||||
* This property's getter returns [Rect.left], and its setter uses [Rect.offsetTo], which
|
||||
* sets [Rect.left] to the new value and offsets [Rect.right] so that the width of the Rect
|
||||
* does not change.
|
||||
*/
|
||||
@JvmField
|
||||
val RECT_X = object : FloatPropertyCompat<Rect>("RectX") {
|
||||
override fun setValue(rect: Rect?, value: Float) {
|
||||
rect?.offsetTo(value.toInt(), rect.top)
|
||||
}
|
||||
|
||||
override fun getValue(rect: Rect?): Float {
|
||||
return rect?.left?.toFloat() ?: -Float.MAX_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the y-coordinate of a [Rect]. Typically used to animate moving a Rect
|
||||
* vertically.
|
||||
*
|
||||
* This property's getter returns [Rect.top], and its setter uses [Rect.offsetTo], which
|
||||
* sets [Rect.top] to the new value and offsets [Rect.bottom] so that the height of the Rect
|
||||
* does not change.
|
||||
*/
|
||||
@JvmField
|
||||
val RECT_Y = object : FloatPropertyCompat<Rect>("RectY") {
|
||||
override fun setValue(rect: Rect?, value: Float) {
|
||||
rect?.offsetTo(rect.left, value.toInt())
|
||||
}
|
||||
|
||||
override fun getValue(rect: Rect?): Float {
|
||||
return rect?.top?.toFloat() ?: -Float.MAX_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF
|
||||
* horizontally.
|
||||
*
|
||||
* This property's getter returns [RectF.left], and its setter uses [RectF.offsetTo], which
|
||||
* sets [RectF.left] to the new value and offsets [RectF.right] so that the width of the
|
||||
* RectF does not change.
|
||||
*/
|
||||
@JvmField
|
||||
val RECTF_X = object : FloatPropertyCompat<RectF>("RectFX") {
|
||||
override fun setValue(rect: RectF?, value: Float) {
|
||||
rect?.offsetTo(value, rect.top)
|
||||
}
|
||||
|
||||
override fun getValue(rect: RectF?): Float {
|
||||
return rect?.left ?: -Float.MAX_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the y-coordinate of a [RectF]. Typically used to animate moving a RectF
|
||||
* vertically.
|
||||
*
|
||||
* This property's getter returns [RectF.top], and its setter uses [RectF.offsetTo], which
|
||||
* sets [RectF.top] to the new value and offsets [RectF.bottom] so that the height of the
|
||||
* RectF does not change.
|
||||
*/
|
||||
@JvmField
|
||||
val RECTF_Y = object : FloatPropertyCompat<RectF>("RectFY") {
|
||||
override fun setValue(rect: RectF?, value: Float) {
|
||||
rect?.offsetTo(rect.left, value)
|
||||
}
|
||||
|
||||
override fun getValue(rect: RectF?): Float {
|
||||
return rect?.top ?: -Float.MAX_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,15 +293,19 @@ class PhysicsAnimator<T> private constructor (val target: T) {
|
||||
val velocityToReachDestination = distanceToDestination *
|
||||
(flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
|
||||
|
||||
// Try to use the provided start velocity, but use the required velocity to reach the
|
||||
// destination if the provided velocity is insufficient.
|
||||
val sufficientVelocity =
|
||||
if (distanceToDestination < 0)
|
||||
min(velocityToReachDestination, startVelocity)
|
||||
else
|
||||
max(velocityToReachDestination, startVelocity)
|
||||
// If there's distance to cover, and the provided velocity is moving in the correct
|
||||
// direction, ensure that the velocity is high enough to reach the destination.
|
||||
// Otherwise, just use startVelocity - this means that the fling is at or out of bounds.
|
||||
// The fling will immediately end and a spring will bring the object back into bounds
|
||||
// with this startVelocity.
|
||||
flingConfigCopy.startVelocity = when {
|
||||
distanceToDestination > 0f && startVelocity >= 0f ->
|
||||
max(velocityToReachDestination, startVelocity)
|
||||
distanceToDestination < 0f && startVelocity <= 0f ->
|
||||
min(velocityToReachDestination, startVelocity)
|
||||
else -> startVelocity
|
||||
}
|
||||
|
||||
flingConfigCopy.startVelocity = sufficientVelocity
|
||||
springConfigCopy.finalPosition = toAtLeast
|
||||
} else {
|
||||
flingConfigCopy.startVelocity = startVelocity
|
||||
@@ -367,8 +371,17 @@ class PhysicsAnimator<T> private constructor (val target: T) {
|
||||
* animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
|
||||
* which indicates that all relevant animations have ended.
|
||||
*/
|
||||
fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
|
||||
this.endActions.addAll(endActions)
|
||||
fun withEndActions(vararg endActions: EndAction?): PhysicsAnimator<T> {
|
||||
this.endActions.addAll(endActions.filterNotNull())
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper overload so that callers from Java can use Runnables or method references as end
|
||||
* actions without having to explicitly return Unit.
|
||||
*/
|
||||
fun withEndActions(vararg endActions: Runnable?): PhysicsAnimator<T> {
|
||||
this.endActions.addAll(endActions.filterNotNull().map { it::run })
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -416,8 +429,10 @@ class PhysicsAnimator<T> private constructor (val target: T) {
|
||||
max = max(currentValue, this.max)
|
||||
}
|
||||
|
||||
// Apply the configuration and start the animation.
|
||||
// Apply the configuration and start the animation. Since flings can't be
|
||||
// redirected while in motion, cancel it first.
|
||||
getFlingAnimation(animatedProperty)
|
||||
.also { it.cancel() }
|
||||
.also { flingConfig.applyToAnimation(it) }
|
||||
.start()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user