Merge "Scale down PIP when it's dragged into the dismiss target." into rvc-dev am: 7a1cd3f757 am: 22c5e25a5b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11886098 Change-Id: I2ece9747075a3c6e5a0901664c37807c0ef91106
This commit is contained in:
@@ -708,6 +708,12 @@ public class PipTaskOrganizer extends TaskOrganizer implements
|
||||
Log.w(TAG, "Abort animation, invalid leash");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startBounds.isEmpty() || destinationBounds.isEmpty()) {
|
||||
Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
|
||||
mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds);
|
||||
tx.apply();
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Debug;
|
||||
import android.util.Log;
|
||||
@@ -38,6 +39,9 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function0;
|
||||
|
||||
/**
|
||||
* A helper to animate and manipulate the PiP.
|
||||
*/
|
||||
@@ -74,9 +78,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
new SfVsyncFrameCallbackProvider();
|
||||
|
||||
/**
|
||||
* Bounds that are animated using the physics animator.
|
||||
* Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP
|
||||
* using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into
|
||||
* and expanding out of the magnetic dismiss target.
|
||||
*
|
||||
* Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary
|
||||
* bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to
|
||||
* its new bounds.
|
||||
*/
|
||||
private final Rect mAnimatedBounds = new Rect();
|
||||
private final Rect mTemporaryBounds = new Rect();
|
||||
|
||||
/** The destination bounds to which PIP is animating. */
|
||||
private final Rect mAnimatingToBounds = new Rect();
|
||||
@@ -85,20 +95,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
private FloatingContentCoordinator mFloatingContentCoordinator;
|
||||
|
||||
/** Callback that re-sizes PIP to the animated bounds. */
|
||||
private final Choreographer.FrameCallback mResizePipVsyncCallback =
|
||||
l -> resizePipUnchecked(mAnimatedBounds);
|
||||
private final Choreographer.FrameCallback mResizePipVsyncCallback;
|
||||
|
||||
/**
|
||||
* PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations.
|
||||
* PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations.
|
||||
*/
|
||||
private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
|
||||
mAnimatedBounds);
|
||||
private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
|
||||
mTemporaryBounds);
|
||||
|
||||
private MagnetizedObject<Rect> mMagnetizedPip;
|
||||
|
||||
/**
|
||||
* Update listener that resizes the PIP to {@link #mAnimatedBounds}.
|
||||
* Update listener that resizes the PIP to {@link #mTemporaryBounds}.
|
||||
*/
|
||||
final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
|
||||
(target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
|
||||
private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
|
||||
|
||||
/** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
|
||||
private PhysicsAnimator.FlingConfig mFlingConfigX;
|
||||
@@ -123,6 +133,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
*/
|
||||
private boolean mSpringingToTouch = false;
|
||||
|
||||
/**
|
||||
* Whether PIP was released in the dismiss target, and will be animated out and dismissed
|
||||
* shortly.
|
||||
*/
|
||||
private boolean mDismissalPending = false;
|
||||
|
||||
/**
|
||||
* Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
|
||||
* used to show menu activity when the expand animation is completed.
|
||||
@@ -155,6 +171,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
mSnapAlgorithm = snapAlgorithm;
|
||||
mFloatingContentCoordinator = floatingContentCoordinator;
|
||||
mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
|
||||
|
||||
mResizePipVsyncCallback = l -> {
|
||||
if (!mTemporaryBounds.isEmpty()) {
|
||||
mPipTaskOrganizer.scheduleUserResizePip(
|
||||
mBounds, mTemporaryBounds, null);
|
||||
}
|
||||
};
|
||||
|
||||
mResizePipUpdateListener = (target, values) ->
|
||||
mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -186,19 +212,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This
|
||||
* is done to prepare for a touch gesture.
|
||||
*/
|
||||
void synchronizePinnedStackBoundsForTouchGesture() {
|
||||
if (mAnimatingToBounds.isEmpty()) {
|
||||
// If we're not animating anywhere, sync normally.
|
||||
synchronizePinnedStackBounds();
|
||||
} else {
|
||||
// If we're animating, set the current bounds to the animated bounds. That way, the
|
||||
// touch gesture will begin at the most recent animated location of the bounds.
|
||||
mBounds.set(mAnimatedBounds);
|
||||
}
|
||||
boolean isAnimating() {
|
||||
return mTemporaryBoundsPhysicsAnimator.isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,32 +239,54 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
// If we are moving PIP directly to the touch event locations, cancel any animations and
|
||||
// move PIP to the given bounds.
|
||||
cancelAnimations();
|
||||
resizePipUnchecked(toBounds);
|
||||
mBounds.set(toBounds);
|
||||
|
||||
if (!isDragging) {
|
||||
resizePipUnchecked(toBounds);
|
||||
mBounds.set(toBounds);
|
||||
} else {
|
||||
mTemporaryBounds.set(toBounds);
|
||||
mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, null);
|
||||
}
|
||||
} else {
|
||||
// If PIP is 'catching up' after being stuck in the dismiss target, update the animation
|
||||
// to spring towards the new touch location.
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
mTemporaryBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig)
|
||||
.spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig)
|
||||
.spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig)
|
||||
.withEndActions(() -> mSpringingToTouch = false);
|
||||
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
|
||||
|
||||
startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */,
|
||||
false /* dismiss */);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
|
||||
void setSpringingToTouch(boolean springingToTouch) {
|
||||
if (springingToTouch) {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
}
|
||||
/** Animates the PIP into the dismiss target, scaling it down. */
|
||||
void animateIntoDismissTarget(
|
||||
MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY,
|
||||
boolean flung, Function0<Unit> after) {
|
||||
final PointF targetCenter = target.getCenterOnScreen();
|
||||
|
||||
mSpringingToTouch = springingToTouch;
|
||||
final float desiredWidth = mBounds.width() / 2;
|
||||
final float desiredHeight = mBounds.height() / 2;
|
||||
|
||||
final float destinationX = targetCenter.x - (desiredWidth / 2f);
|
||||
final float destinationY = targetCenter.y - (desiredHeight / 2f);
|
||||
|
||||
mTemporaryBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig)
|
||||
.withEndActions(after);
|
||||
|
||||
startBoundsAnimator(destinationX, destinationY, false);
|
||||
}
|
||||
|
||||
void prepareForAnimation() {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
/** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
|
||||
void setSpringingToTouch(boolean springingToTouch) {
|
||||
mSpringingToTouch = springingToTouch;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,14 +345,23 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
|
||||
* otherwise.
|
||||
*/
|
||||
Rect getPossiblyAnimatingBounds() {
|
||||
return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flings the PiP to the closest snap target.
|
||||
*/
|
||||
void flingToSnapTarget(
|
||||
float velocityX, float velocityY,
|
||||
@Nullable Runnable updateAction, @Nullable Runnable endAction) {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
mTemporaryBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig)
|
||||
.spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig)
|
||||
.flingThenSpring(
|
||||
FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig,
|
||||
true /* flingMustReachMinOrMax */)
|
||||
@@ -324,13 +370,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
.withEndActions(endAction);
|
||||
|
||||
if (updateAction != null) {
|
||||
mAnimatedBoundsPhysicsAnimator.addUpdateListener(
|
||||
mTemporaryBoundsPhysicsAnimator.addUpdateListener(
|
||||
(target, values) -> updateAction.run());
|
||||
}
|
||||
|
||||
final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right;
|
||||
final float estimatedFlingYEndValue =
|
||||
PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
|
||||
PhysicsAnimator.estimateFlingEndValue(
|
||||
mTemporaryBounds.top, velocityY, mFlingConfigY);
|
||||
|
||||
startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
|
||||
false /* dismiss */);
|
||||
@@ -341,8 +388,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
* configuration
|
||||
*/
|
||||
void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
|
||||
// Animate from the current bounds if we're not already animating.
|
||||
mTemporaryBounds.set(mBounds);
|
||||
}
|
||||
|
||||
mTemporaryBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, bounds.left, springConfig)
|
||||
.spring(FloatProperties.RECT_Y, bounds.top, springConfig);
|
||||
startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */,
|
||||
@@ -353,18 +404,19 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
* Animates the dismissal of the PiP off the edge of the screen.
|
||||
*/
|
||||
void animateDismiss() {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
|
||||
// Animate off the bottom of the screen, then dismiss PIP.
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
mTemporaryBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_Y,
|
||||
mBounds.bottom + mBounds.height(),
|
||||
mMovementBounds.bottom + mBounds.height() * 2,
|
||||
0,
|
||||
mSpringConfig)
|
||||
.withEndActions(this::dismissPip);
|
||||
|
||||
startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */,
|
||||
startBoundsAnimator(
|
||||
mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */,
|
||||
true /* dismiss */);
|
||||
|
||||
mDismissalPending = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,7 +467,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
* Cancels all existing animations.
|
||||
*/
|
||||
private void cancelAnimations() {
|
||||
mAnimatedBoundsPhysicsAnimator.cancel();
|
||||
mTemporaryBoundsPhysicsAnimator.cancel();
|
||||
mAnimatingToBounds.setEmpty();
|
||||
mSpringingToTouch = false;
|
||||
}
|
||||
@@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
(int) toY + mBounds.height());
|
||||
setAnimatingToBounds(mAnimatingToBounds);
|
||||
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.withEndActions(() -> {
|
||||
if (!dismiss) {
|
||||
mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds);
|
||||
}
|
||||
mAnimatingToBounds.setEmpty();
|
||||
})
|
||||
.addUpdateListener(mResizePipUpdateListener)
|
||||
.start();
|
||||
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
|
||||
mTemporaryBoundsPhysicsAnimator
|
||||
.addUpdateListener(mResizePipUpdateListener)
|
||||
.withEndActions(this::onBoundsAnimationEnd);
|
||||
}
|
||||
|
||||
mTemporaryBoundsPhysicsAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that PIP was released in the dismiss target and will be animated out and dismissed
|
||||
* shortly.
|
||||
*/
|
||||
void notifyDismissalPending() {
|
||||
mDismissalPending = true;
|
||||
}
|
||||
|
||||
private void onBoundsAnimationEnd() {
|
||||
if (!mDismissalPending
|
||||
&& !mSpringingToTouch
|
||||
&& !mMagnetizedPip.getObjectStuckToTarget()) {
|
||||
mBounds.set(mTemporaryBounds);
|
||||
mPipTaskOrganizer.scheduleFinishResizePip(mBounds);
|
||||
|
||||
mTemporaryBounds.setEmpty();
|
||||
}
|
||||
|
||||
mAnimatingToBounds.setEmpty();
|
||||
mSpringingToTouch = false;
|
||||
mDismissalPending = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -503,25 +576,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
* magnetic dismiss target so it can calculate PIP's size and position.
|
||||
*/
|
||||
MagnetizedObject<Rect> getMagnetizedPip() {
|
||||
return new MagnetizedObject<Rect>(
|
||||
mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull Rect animatedPipBounds) {
|
||||
return animatedPipBounds.width();
|
||||
}
|
||||
if (mMagnetizedPip == null) {
|
||||
mMagnetizedPip = new MagnetizedObject<Rect>(
|
||||
mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull Rect animatedPipBounds) {
|
||||
return animatedPipBounds.width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(@NonNull Rect animatedPipBounds) {
|
||||
return animatedPipBounds.height();
|
||||
}
|
||||
@Override
|
||||
public float getHeight(@NonNull Rect animatedPipBounds) {
|
||||
return animatedPipBounds.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocationOnScreen(
|
||||
@NonNull Rect animatedPipBounds, @NonNull int[] loc) {
|
||||
loc[0] = animatedPipBounds.left;
|
||||
loc[1] = animatedPipBounds.top;
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void getLocationOnScreen(
|
||||
@NonNull Rect animatedPipBounds, @NonNull int[] loc) {
|
||||
loc[0] = animatedPipBounds.left;
|
||||
loc[1] = animatedPipBounds.top;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return mMagnetizedPip;
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
|
||||
@@ -70,6 +70,8 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
|
||||
* the PIP.
|
||||
@@ -262,12 +264,14 @@ public class PipTouchHandler {
|
||||
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
|
||||
updateMagneticTargetSize();
|
||||
|
||||
mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
|
||||
mMagnetizedPip.setAnimateStuckToTarget(
|
||||
(target, velX, velY, flung, after) -> {
|
||||
mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mMotionHelper.prepareForAnimation();
|
||||
|
||||
// Show the dismiss target, in case the initial touch event occurred within the
|
||||
// magnetic field radius.
|
||||
showDismissTargetMaybe();
|
||||
@@ -286,12 +290,13 @@ public class PipTouchHandler {
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mMotionHelper.notifyDismissalPending();
|
||||
|
||||
mHandler.post(() -> {
|
||||
mMotionHelper.animateDismiss();
|
||||
hideDismissTarget();
|
||||
});
|
||||
|
||||
|
||||
MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
|
||||
PipUtils.getTopPipActivity(mContext, mActivityManager));
|
||||
}
|
||||
@@ -617,11 +622,16 @@ public class PipTouchHandler {
|
||||
}
|
||||
|
||||
MotionEvent ev = (MotionEvent) inputEvent;
|
||||
|
||||
if (mPipResizeGestureHandler.isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) {
|
||||
if (!mTouchState.isDragging()
|
||||
&& !mMagnetizedPip.getObjectStuckToTarget()
|
||||
&& !mMotionHelper.isAnimating()
|
||||
&& mPipResizeGestureHandler.isWithinTouchRegion(
|
||||
(int) ev.getRawX(), (int) ev.getRawY())) {
|
||||
return true;
|
||||
}
|
||||
if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
|
||||
|
||||
if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
|
||||
&& mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
|
||||
// If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
|
||||
// to the touch state. Touch state needs a DOWN event in order to later process MOVE
|
||||
// events it'll receive if the object is dragged out of the magnetic field.
|
||||
@@ -643,7 +653,6 @@ public class PipTouchHandler {
|
||||
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mMotionHelper.synchronizePinnedStackBoundsForTouchGesture();
|
||||
mGesture.onDown(mTouchState);
|
||||
break;
|
||||
}
|
||||
@@ -872,7 +881,7 @@ public class PipTouchHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect bounds = mMotionHelper.getBounds();
|
||||
Rect bounds = mMotionHelper.getPossiblyAnimatingBounds();
|
||||
mDelta.set(0f, 0f);
|
||||
mStartPosition.set(bounds.left, bounds.top);
|
||||
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
|
||||
@@ -914,7 +923,7 @@ public class PipTouchHandler {
|
||||
mDelta.x += left - lastX;
|
||||
mDelta.y += top - lastY;
|
||||
|
||||
mTmpBounds.set(mMotionHelper.getBounds());
|
||||
mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds());
|
||||
mTmpBounds.offsetTo((int) left, (int) top);
|
||||
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
|
||||
|
||||
|
||||
@@ -66,6 +66,40 @@ class FloatProperties {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the width of a [Rect]. Typically used to animate resizing a Rect horizontally.
|
||||
*
|
||||
* This property's getter returns [Rect.width], and its setter changes the value of
|
||||
* [Rect.right] by adding the animated width value to [Rect.left].
|
||||
*/
|
||||
@JvmField
|
||||
val RECT_WIDTH = object : FloatPropertyCompat<Rect>("RectWidth") {
|
||||
override fun getValue(rect: Rect): Float {
|
||||
return rect.width().toFloat()
|
||||
}
|
||||
|
||||
override fun setValue(rect: Rect, value: Float) {
|
||||
rect.right = rect.left + value.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the height of a [Rect]. Typically used to animate resizing a Rect vertically.
|
||||
*
|
||||
* This property's getter returns [Rect.height], and its setter changes the value of
|
||||
* [Rect.bottom] by adding the animated height value to [Rect.top].
|
||||
*/
|
||||
@JvmField
|
||||
val RECT_HEIGHT = object : FloatPropertyCompat<Rect>("RectHeight") {
|
||||
override fun getValue(rect: Rect): Float {
|
||||
return rect.height().toFloat()
|
||||
}
|
||||
|
||||
override fun setValue(rect: Rect, value: Float) {
|
||||
rect.bottom = rect.top + value.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF
|
||||
* horizontally.
|
||||
|
||||
@@ -177,6 +177,18 @@ abstract class MagnetizedObject<T : Any>(
|
||||
*/
|
||||
var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
|
||||
|
||||
/**
|
||||
* Method that is called when the object should be animated stuck to the target. The default
|
||||
* implementation uses the object's x and y properties to animate the object centered inside the
|
||||
* target. You can override this if you need custom animation.
|
||||
*
|
||||
* The method is invoked with the MagneticTarget that the object is sticking to, the X and Y
|
||||
* velocities of the gesture that brought the object into the magnetic radius, whether or not it
|
||||
* was flung, and a callback you must call after your animation completes.
|
||||
*/
|
||||
var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit =
|
||||
::animateStuckToTargetInternal
|
||||
|
||||
/**
|
||||
* Sets whether forcefully flinging the object vertically towards a target causes it to be
|
||||
* attracted to the target and then released immediately, despite never being dragged within the
|
||||
@@ -373,7 +385,7 @@ abstract class MagnetizedObject<T : Any>(
|
||||
targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
|
||||
cancelAnimations()
|
||||
magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
|
||||
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false)
|
||||
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null)
|
||||
|
||||
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
|
||||
} else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) {
|
||||
@@ -430,8 +442,8 @@ abstract class MagnetizedObject<T : Any>(
|
||||
targetObjectIsStuckTo = flungToTarget
|
||||
|
||||
animateStuckToTarget(flungToTarget, velX, velY, true) {
|
||||
targetObjectIsStuckTo = null
|
||||
magnetListener.onReleasedInTarget(flungToTarget)
|
||||
targetObjectIsStuckTo = null
|
||||
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
|
||||
}
|
||||
|
||||
@@ -465,7 +477,7 @@ abstract class MagnetizedObject<T : Any>(
|
||||
}
|
||||
|
||||
/** Animates sticking the object to the provided target with the given start velocities. */
|
||||
private fun animateStuckToTarget(
|
||||
private fun animateStuckToTargetInternal(
|
||||
target: MagneticTarget,
|
||||
velX: Float,
|
||||
velY: Float,
|
||||
@@ -581,10 +593,10 @@ abstract class MagnetizedObject<T : Any>(
|
||||
* multiple objects.
|
||||
*/
|
||||
class MagneticTarget(
|
||||
internal val targetView: View,
|
||||
val targetView: View,
|
||||
var magneticFieldRadiusPx: Int
|
||||
) {
|
||||
internal val centerOnScreen = PointF()
|
||||
val centerOnScreen = PointF()
|
||||
|
||||
private val tempLoc = IntArray(2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user