summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Josh Tsuji <tsuji@google.com> 2020-06-16 17:00:59 -0400
committer Josh Tsuji <tsuji@google.com> 2020-06-17 11:45:42 -0400
commita8ed97ee016fa1fb6d62f8a08e196868df9a2ff7 (patch)
treecd980790c148c0cdcd0acee9f9c6523a76418d9e
parentf21036b4e7bd6cb9a91cba2aca57764b1592d386 (diff)
Scale down PIP when it's dragged into the dismiss target.
To do this reliably, this CL changes the motion helper logic such that all dragging and animating changes the 'temporary bounds', which are applied to PIP via userResize. When a drag/animation ends, we schedule a finishResizePip to 'actually' move PIP to its final location. This also fixes an issue where the resize gesture could steal events during a drag. Bug: 158247953 Fixes: 157264677 Test: manual Change-Id: I0129a4c77d06fb9cd186eacd4a35975f54432e9d
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java227
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt22
5 files changed, 228 insertions, 90 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index b93e07e65c73..9daa876038d5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -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();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 856c19290af6..06c98d00cca7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -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;
@@ -124,6 +134,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;
}
/**
@@ -309,13 +346,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
/**
+ * 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();
- }
-
- @Override
- public float getHeight(@NonNull Rect animatedPipBounds) {
- return animatedPipBounds.height();
- }
+ 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 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) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 3cc9127068bf..5de7e0e42130 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -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 */);
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
index ecd3afd687b3..a284a747da21 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
@@ -67,6 +67,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.
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index 5a2b064c5389..47b607fc6285 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -178,6 +178,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
* magnetic field.
@@ -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)