Merge changes Ib5b3b032,I81147568,If9218d23 into rvc-dev
* changes: Fix LayoutParams issue with DismissCircleView. Modifies Bubbles to use the MagnetizedObject library. Adds DismissCircleView, a shared dismiss target for Bubbles/PIP.
This commit is contained in:
28
packages/SystemUI/res/drawable/dismiss_circle_background.xml
Normal file
28
packages/SystemUI/res/drawable/dismiss_circle_background.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#66FFFFFF" />
|
||||
|
||||
<solid android:color="#B3000000" />
|
||||
|
||||
</shape>
|
||||
28
packages/SystemUI/res/drawable/dismiss_target_x.xml
Normal file
28
packages/SystemUI/res/drawable/dismiss_target_x.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<!-- 'X' icon. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:strokeColor="#FF000000"/>
|
||||
</vector>
|
||||
@@ -1183,6 +1183,9 @@
|
||||
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
|
||||
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
|
||||
|
||||
<dimen name="dismiss_circle_size">52dp</dimen>
|
||||
<dimen name="dismiss_target_x_size">24dp</dimen>
|
||||
|
||||
<!-- Bubbles user education views -->
|
||||
<dimen name="bubbles_manage_education_width">160dp</dimen>
|
||||
<!-- The inset from the top bound of the manage button to place the user education. -->
|
||||
|
||||
@@ -32,7 +32,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
@@ -47,7 +46,6 @@ import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.Choreographer;
|
||||
@@ -56,6 +54,7 @@ import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
@@ -66,6 +65,7 @@ import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
@@ -81,7 +81,10 @@ import com.android.systemui.bubbles.animation.ExpandedAnimationController;
|
||||
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
|
||||
import com.android.systemui.bubbles.animation.StackAnimationController;
|
||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||
import com.android.systemui.util.DismissCircleView;
|
||||
import com.android.systemui.util.FloatingContentCoordinator;
|
||||
import com.android.systemui.util.animation.PhysicsAnimator;
|
||||
import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -227,8 +230,6 @@ public class BubbleStackView extends FrameLayout {
|
||||
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
|
||||
pw.print(" showingDismiss: "); pw.println(mShowingDismiss);
|
||||
pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
|
||||
pw.print(" draggingInDismiss: "); pw.println(mDraggingInDismissTarget);
|
||||
pw.print(" animatingMagnet: "); pw.println(mAnimatingMagnet);
|
||||
mStackAnimationController.dump(fd, pw, args);
|
||||
mExpandedAnimationController.dump(fd, pw, args);
|
||||
}
|
||||
@@ -240,16 +241,6 @@ public class BubbleStackView extends FrameLayout {
|
||||
private boolean mIsExpansionAnimating = false;
|
||||
private boolean mShowingDismiss = false;
|
||||
|
||||
/**
|
||||
* Whether the user is currently dragging their finger within the dismiss target. In this state
|
||||
* the stack will be magnetized to the center of the target, so we shouldn't move it until the
|
||||
* touch exits the dismiss target area.
|
||||
*/
|
||||
private boolean mDraggingInDismissTarget = false;
|
||||
|
||||
/** Whether the stack is magneting towards the dismiss target. */
|
||||
private boolean mAnimatingMagnet = false;
|
||||
|
||||
/** The view to desaturate/darken when magneted to the dismiss target. */
|
||||
private View mDesaturateAndDarkenTargetView;
|
||||
|
||||
@@ -331,8 +322,100 @@ public class BubbleStackView extends FrameLayout {
|
||||
@NonNull
|
||||
private final SurfaceSynchronizer mSurfaceSynchronizer;
|
||||
|
||||
private BubbleDismissView mDismissContainer;
|
||||
private Runnable mAfterMagnet;
|
||||
/**
|
||||
* The currently magnetized object, which is being dragged and will be attracted to the magnetic
|
||||
* dismiss target.
|
||||
*
|
||||
* This is either the stack itself, or an individual bubble.
|
||||
*/
|
||||
private MagnetizedObject<?> mMagnetizedObject;
|
||||
|
||||
/**
|
||||
* The action to run when the magnetized object is released in the dismiss target.
|
||||
*
|
||||
* This will actually perform the dismissal of either the stack or an individual bubble.
|
||||
*/
|
||||
private Runnable mReleasedInDismissTargetAction;
|
||||
|
||||
/**
|
||||
* The MagneticTarget instance for our circular dismiss view. This is added to the
|
||||
* MagnetizedObject instances for the stack and any dragged-out bubbles.
|
||||
*/
|
||||
private MagnetizedObject.MagneticTarget mMagneticTarget;
|
||||
|
||||
/** Magnet listener that handles animating and dismissing individual dragged-out bubbles. */
|
||||
private final MagnetizedObject.MagnetListener mIndividualBubbleMagnetListener =
|
||||
new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
animateDesaturateAndDarken(
|
||||
mExpandedAnimationController.getDraggedOutBubble(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
animateDesaturateAndDarken(
|
||||
mExpandedAnimationController.getDraggedOutBubble(), false);
|
||||
|
||||
if (wasFlungOut) {
|
||||
mExpandedAnimationController.snapBubbleBack(
|
||||
mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
|
||||
hideDismissTarget();
|
||||
} else {
|
||||
mExpandedAnimationController.onUnstuckFromTarget();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mExpandedAnimationController.dismissDraggedOutBubble(
|
||||
mExpandedAnimationController.getDraggedOutBubble(),
|
||||
mReleasedInDismissTargetAction);
|
||||
hideDismissTarget();
|
||||
}
|
||||
};
|
||||
|
||||
/** Magnet listener that handles animating and dismissing the entire stack. */
|
||||
private final MagnetizedObject.MagnetListener mStackMagnetListener =
|
||||
new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(
|
||||
@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
animateDesaturateAndDarken(mBubbleContainer, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
animateDesaturateAndDarken(mBubbleContainer, false);
|
||||
|
||||
if (wasFlungOut) {
|
||||
mStackAnimationController.flingStackThenSpringToEdge(
|
||||
mStackAnimationController.getStackPosition().x, velX, velY);
|
||||
hideDismissTarget();
|
||||
} else {
|
||||
mStackAnimationController.onUnstuckFromTarget();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mStackAnimationController.implodeStack(
|
||||
() -> {
|
||||
resetDesaturationAndDarken();
|
||||
mReleasedInDismissTargetAction.run();
|
||||
}
|
||||
);
|
||||
|
||||
hideDismissTarget();
|
||||
}
|
||||
};
|
||||
|
||||
private ViewGroup mDismissTargetContainer;
|
||||
private PhysicsAnimator<View> mDismissTargetAnimator;
|
||||
private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
|
||||
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
|
||||
|
||||
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
|
||||
|
||||
@@ -409,12 +492,31 @@ public class BubbleStackView extends FrameLayout {
|
||||
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
|
||||
mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
|
||||
|
||||
mDismissContainer = new BubbleDismissView(mContext);
|
||||
mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
|
||||
final View targetView = new DismissCircleView(context);
|
||||
final FrameLayout.LayoutParams newParams =
|
||||
new FrameLayout.LayoutParams(targetSize, targetSize);
|
||||
newParams.gravity = Gravity.CENTER;
|
||||
targetView.setLayoutParams(newParams);
|
||||
mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView);
|
||||
|
||||
mDismissTargetContainer = new FrameLayout(context);
|
||||
mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
MATCH_PARENT,
|
||||
getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
|
||||
Gravity.BOTTOM));
|
||||
addView(mDismissContainer);
|
||||
mDismissTargetContainer.setClipChildren(false);
|
||||
mDismissTargetContainer.addView(targetView);
|
||||
mDismissTargetContainer.setVisibility(View.INVISIBLE);
|
||||
addView(mDismissTargetContainer);
|
||||
|
||||
// Start translated down so the target springs up.
|
||||
targetView.setTranslationY(
|
||||
getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height));
|
||||
|
||||
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
|
||||
// MagnetizedObjects.
|
||||
mMagneticTarget = new MagnetizedObject.MagneticTarget(targetView, mBubbleSize * 2);
|
||||
|
||||
mExpandedViewXAnim =
|
||||
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
|
||||
@@ -1066,6 +1168,14 @@ public class BubbleStackView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the action to run to dismiss the currently dragging object (either the stack or an
|
||||
* individual bubble).
|
||||
*/
|
||||
public void setReleasedInDismissTargetAction(Runnable action) {
|
||||
mReleasedInDismissTargetAction = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the stack of bubbles.
|
||||
*
|
||||
@@ -1262,7 +1372,12 @@ public class BubbleStackView extends FrameLayout {
|
||||
Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
|
||||
}
|
||||
maybeShowManageEducation(false);
|
||||
mExpandedAnimationController.prepareForBubbleDrag(bubble);
|
||||
mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget);
|
||||
|
||||
// We're dragging an individual bubble, so set the magnetized object to the magnetized
|
||||
// bubble.
|
||||
mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
|
||||
mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener);
|
||||
}
|
||||
|
||||
/** Called with the coordinates to which an individual bubble has been dragged. */
|
||||
@@ -1304,7 +1419,9 @@ public class BubbleStackView extends FrameLayout {
|
||||
mBubbleContainer.setActiveController(mStackAnimationController);
|
||||
hideFlyoutImmediate();
|
||||
|
||||
mDraggingInDismissTarget = false;
|
||||
// Since we're dragging the stack, set the magnetized object to the magnetized stack.
|
||||
mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
|
||||
mMagnetizedObject.setMagnetListener(mStackMagnetListener);
|
||||
}
|
||||
|
||||
void onDragged(float x, float y) {
|
||||
@@ -1425,6 +1542,11 @@ public class BubbleStackView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
|
||||
boolean passEventToMagnetizedObject(MotionEvent event) {
|
||||
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
|
||||
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
|
||||
mDesaturateAndDarkenTargetView = targetView;
|
||||
@@ -1455,102 +1577,6 @@ public class BubbleStackView extends FrameLayout {
|
||||
mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magnets the stack to the target, while also transforming the target to encircle the stack and
|
||||
* desaturating/darkening the bubbles.
|
||||
*/
|
||||
void animateMagnetToDismissTarget(
|
||||
View magnetView, boolean toTarget, float x, float y, float velX, float velY) {
|
||||
mDraggingInDismissTarget = toTarget;
|
||||
|
||||
if (toTarget) {
|
||||
// The Y-value for the bubble stack to be positioned in the center of the dismiss target
|
||||
final float destY = mDismissContainer.getDismissTargetCenterY() - mBubbleSize / 2f;
|
||||
|
||||
mAnimatingMagnet = true;
|
||||
|
||||
final Runnable afterMagnet = () -> {
|
||||
mAnimatingMagnet = false;
|
||||
if (mAfterMagnet != null) {
|
||||
mAfterMagnet.run();
|
||||
}
|
||||
};
|
||||
|
||||
if (magnetView == this) {
|
||||
mStackAnimationController.magnetToDismiss(velX, velY, destY, afterMagnet);
|
||||
animateDesaturateAndDarken(mBubbleContainer, true);
|
||||
} else {
|
||||
mExpandedAnimationController.magnetBubbleToDismiss(
|
||||
magnetView, velX, velY, destY, afterMagnet);
|
||||
|
||||
animateDesaturateAndDarken(magnetView, true);
|
||||
}
|
||||
} else {
|
||||
mAnimatingMagnet = false;
|
||||
|
||||
if (magnetView == this) {
|
||||
mStackAnimationController.demagnetizeFromDismissToPoint(x, y, velX, velY);
|
||||
animateDesaturateAndDarken(mBubbleContainer, false);
|
||||
} else {
|
||||
mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
|
||||
animateDesaturateAndDarken(magnetView, false);
|
||||
}
|
||||
}
|
||||
|
||||
mVibrator.vibrate(VibrationEffect.get(toTarget
|
||||
? VibrationEffect.EFFECT_CLICK
|
||||
: VibrationEffect.EFFECT_TICK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Magnets the stack to the dismiss target if it's not already there. Then, dismiss the stack
|
||||
* using the 'implode' animation and animate out the target.
|
||||
*/
|
||||
void magnetToStackIfNeededThenAnimateDismissal(
|
||||
View touchedView, float velX, float velY, Runnable after) {
|
||||
final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble();
|
||||
final Runnable animateDismissal = () -> {
|
||||
mAfterMagnet = null;
|
||||
|
||||
mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
|
||||
mDismissContainer.springOut();
|
||||
|
||||
// 'Implode' the stack and then hide the dismiss target.
|
||||
if (touchedView == this) {
|
||||
mStackAnimationController.implodeStack(
|
||||
() -> {
|
||||
mAnimatingMagnet = false;
|
||||
mShowingDismiss = false;
|
||||
mDraggingInDismissTarget = false;
|
||||
after.run();
|
||||
resetDesaturationAndDarken();
|
||||
});
|
||||
} else {
|
||||
mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> {
|
||||
mAnimatingMagnet = false;
|
||||
mShowingDismiss = false;
|
||||
mDraggingInDismissTarget = false;
|
||||
resetDesaturationAndDarken();
|
||||
after.run();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (mAnimatingMagnet) {
|
||||
// If the magnet animation is currently playing, dismiss the stack after it's done. This
|
||||
// happens if the stack is flung towards the target.
|
||||
mAfterMagnet = animateDismissal;
|
||||
} else if (mDraggingInDismissTarget) {
|
||||
// If we're in the dismiss target, but not animating, we already magneted - dismiss
|
||||
// immediately.
|
||||
animateDismissal.run();
|
||||
} else {
|
||||
// Otherwise, we need to start the magnet animation and then dismiss afterward.
|
||||
animateMagnetToDismissTarget(touchedView, true, -1 /* x */, -1 /* y */, velX, velY);
|
||||
mAfterMagnet = animateDismissal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Animates in the dismiss target. */
|
||||
private void springInDismissTarget() {
|
||||
if (mShowingDismiss) {
|
||||
@@ -1559,10 +1585,14 @@ public class BubbleStackView extends FrameLayout {
|
||||
|
||||
mShowingDismiss = true;
|
||||
|
||||
// Show the dismiss container and bring it to the front so the bubbles will go behind it.
|
||||
mDismissContainer.springIn();
|
||||
mDismissContainer.bringToFront();
|
||||
mDismissContainer.setZ(Short.MAX_VALUE - 1);
|
||||
mDismissTargetContainer.bringToFront();
|
||||
mDismissTargetContainer.setZ(Short.MAX_VALUE - 1);
|
||||
mDismissTargetContainer.setVisibility(VISIBLE);
|
||||
|
||||
mDismissTargetAnimator.cancel();
|
||||
mDismissTargetAnimator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1574,13 +1604,13 @@ public class BubbleStackView extends FrameLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
mDismissContainer.springOut();
|
||||
mShowingDismiss = false;
|
||||
}
|
||||
|
||||
/** Whether the location of the given MotionEvent is within the dismiss target area. */
|
||||
boolean isInDismissTarget(MotionEvent ev) {
|
||||
return isIntersecting(mDismissContainer.getDismissTarget(), ev.getRawX(), ev.getRawY());
|
||||
mDismissTargetAnimator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(),
|
||||
mDismissTargetSpring)
|
||||
.withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE))
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
|
||||
|
||||
@@ -30,28 +30,6 @@ import com.android.systemui.Dependency;
|
||||
* dismissing, and flings.
|
||||
*/
|
||||
class BubbleTouchHandler implements View.OnTouchListener {
|
||||
/** Velocity required to dismiss the stack without dragging it into the dismiss target. */
|
||||
private static final float STACK_DISMISS_MIN_VELOCITY = 4000f;
|
||||
|
||||
/**
|
||||
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
|
||||
* target.
|
||||
*
|
||||
* This is higher than the stack dismiss velocity since unlike the stack, a downward fling could
|
||||
* also be an attempted gesture to return the bubble to the row of expanded bubbles, which would
|
||||
* usually be below the dragged bubble. By increasing the required velocity, it's less likely
|
||||
* that the user is trying to drop it back into the row vs. fling it away.
|
||||
*/
|
||||
private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
|
||||
|
||||
/**
|
||||
* When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
|
||||
* towards the center of the screen (where the dismiss target is). This value is the width of
|
||||
* the target area to be considered 'towards the target'. For example 50% means that the stack
|
||||
* needs to be flung towards the middle 50%, and the 25% on the left and right sides won't
|
||||
* count.
|
||||
*/
|
||||
private static final float DISMISS_FLING_TARGET_WIDTH_PERCENT = 0.5f;
|
||||
|
||||
private final PointF mTouchDown = new PointF();
|
||||
private final PointF mViewPositionOnTouchDown = new PointF();
|
||||
@@ -66,8 +44,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
|
||||
|
||||
/** View that was initially touched, when we received the first ACTION_DOWN event. */
|
||||
private View mTouchedView;
|
||||
/** Whether the current touched view is in the dismiss target. */
|
||||
private boolean mInDismissTarget;
|
||||
|
||||
BubbleTouchHandler(BubbleStackView stackView,
|
||||
BubbleData bubbleData, Context context) {
|
||||
@@ -124,13 +100,33 @@ class BubbleTouchHandler implements View.OnTouchListener {
|
||||
|
||||
if (isStack) {
|
||||
mViewPositionOnTouchDown.set(mStack.getStackPosition());
|
||||
|
||||
// Dismiss the entire stack if it's released in the dismiss target.
|
||||
mStack.setReleasedInDismissTargetAction(
|
||||
() -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE));
|
||||
mStack.onDragStart();
|
||||
mStack.passEventToMagnetizedObject(event);
|
||||
} else if (isFlyout) {
|
||||
mStack.onFlyoutDragStart();
|
||||
} else {
|
||||
mViewPositionOnTouchDown.set(
|
||||
mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
|
||||
|
||||
// Dismiss only the dragged-out bubble if it's released in the target.
|
||||
final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey();
|
||||
mStack.setReleasedInDismissTargetAction(() -> {
|
||||
final Bubble bubble =
|
||||
mBubbleData.getBubbleWithKey(individualBubbleKey);
|
||||
// bubble can be null if the user is in the middle of
|
||||
// dismissing the bubble, but the app also sent a cancel
|
||||
if (bubble != null) {
|
||||
mController.removeBubble(bubble.getEntry(),
|
||||
BubbleController.DISMISS_USER_GESTURE);
|
||||
}
|
||||
});
|
||||
|
||||
mStack.onBubbleDragStart(mTouchedView);
|
||||
mStack.passEventToMagnetizedObject(event);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -144,27 +140,16 @@ class BubbleTouchHandler implements View.OnTouchListener {
|
||||
}
|
||||
|
||||
if (mMovedEnough) {
|
||||
if (isStack) {
|
||||
mStack.onDragged(viewX, viewY);
|
||||
} else if (isFlyout) {
|
||||
if (isFlyout) {
|
||||
mStack.onFlyoutDragged(deltaX);
|
||||
} else {
|
||||
mStack.onBubbleDragged(mTouchedView, viewX, viewY);
|
||||
}
|
||||
}
|
||||
|
||||
final boolean currentlyInDismissTarget = mStack.isInDismissTarget(event);
|
||||
if (currentlyInDismissTarget != mInDismissTarget) {
|
||||
mInDismissTarget = currentlyInDismissTarget;
|
||||
|
||||
mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
|
||||
final float velX = mVelocityTracker.getXVelocity();
|
||||
final float velY = mVelocityTracker.getYVelocity();
|
||||
|
||||
// If the touch event is within the dismiss target, magnet the stack to it.
|
||||
if (!isFlyout) {
|
||||
mStack.animateMagnetToDismissTarget(
|
||||
mTouchedView, mInDismissTarget, viewX, viewY, velX, velY);
|
||||
} else if (!mStack.passEventToMagnetizedObject(event)) {
|
||||
// If the magnetic target doesn't consume the event, drag the stack or
|
||||
// bubble.
|
||||
if (isStack) {
|
||||
mStack.onDragged(viewX, viewY);
|
||||
} else {
|
||||
mStack.onBubbleDragged(mTouchedView, viewX, viewY);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -179,42 +164,21 @@ class BubbleTouchHandler implements View.OnTouchListener {
|
||||
final float velX = mVelocityTracker.getXVelocity();
|
||||
final float velY = mVelocityTracker.getYVelocity();
|
||||
|
||||
final boolean shouldDismiss =
|
||||
isStack
|
||||
? mInDismissTarget
|
||||
|| isFastFlingTowardsDismissTarget(rawX, rawY, velX, velY)
|
||||
: mInDismissTarget
|
||||
|| velY > INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY;
|
||||
|
||||
if (isFlyout && mMovedEnough) {
|
||||
mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
|
||||
} else if (shouldDismiss) {
|
||||
final String individualBubbleKey =
|
||||
isStack ? null : ((BadgedImageView) mTouchedView).getKey();
|
||||
mStack.magnetToStackIfNeededThenAnimateDismissal(mTouchedView, velX, velY,
|
||||
() -> {
|
||||
if (isStack) {
|
||||
mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
|
||||
} else {
|
||||
final Bubble bubble =
|
||||
mBubbleData.getBubbleWithKey(individualBubbleKey);
|
||||
// bubble can be null if the user is in the middle of
|
||||
// dismissing the bubble, but the app also sent a cancel
|
||||
if (bubble != null) {
|
||||
mController.removeBubble(bubble.getEntry(),
|
||||
BubbleController.DISMISS_USER_GESTURE);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (isFlyout) {
|
||||
if (!mBubbleData.isExpanded() && !mMovedEnough) {
|
||||
mStack.onFlyoutTapped();
|
||||
}
|
||||
} else if (mMovedEnough) {
|
||||
if (isStack) {
|
||||
mStack.onDragFinish(viewX, viewY, velX, velY);
|
||||
} else {
|
||||
mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
|
||||
if (!mStack.passEventToMagnetizedObject(event)) {
|
||||
// If the magnetic target didn't consume the event, tell the stack to finish
|
||||
// the drag.
|
||||
if (isStack) {
|
||||
mStack.onDragFinish(viewX, viewY, velX, velY);
|
||||
} else {
|
||||
mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
|
||||
}
|
||||
}
|
||||
} else if (mTouchedView == mStack.getExpandedBubbleView()) {
|
||||
mBubbleData.setExpanded(false);
|
||||
@@ -235,45 +199,15 @@ class BubbleTouchHandler implements View.OnTouchListener {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given touch data represents a powerful fling towards the bottom-center of the
|
||||
* screen (the dismiss target).
|
||||
*/
|
||||
private boolean isFastFlingTowardsDismissTarget(
|
||||
float rawX, float rawY, float velX, float velY) {
|
||||
// Not a fling downward towards the target if velocity is zero or negative.
|
||||
if (velY <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float bottomOfScreenInterceptX = rawX;
|
||||
|
||||
// Only do math if the X velocity is non-zero, otherwise X won't change.
|
||||
if (velX != 0) {
|
||||
// Rise over run...
|
||||
final float slope = velY / velX;
|
||||
// ...y = mx + b, b = y / mx...
|
||||
final float yIntercept = rawY - slope * rawX;
|
||||
// ...calculate the x value when y = bottom of the screen.
|
||||
bottomOfScreenInterceptX = (mStack.getHeight() - yIntercept) / slope;
|
||||
}
|
||||
|
||||
final float dismissTargetWidth =
|
||||
mStack.getWidth() * DISMISS_FLING_TARGET_WIDTH_PERCENT;
|
||||
return velY > STACK_DISMISS_MIN_VELOCITY
|
||||
&& bottomOfScreenInterceptX > dismissTargetWidth / 2f
|
||||
&& bottomOfScreenInterceptX < mStack.getWidth() - dismissTargetWidth / 2f;
|
||||
}
|
||||
|
||||
/** Clears all touch-related state. */
|
||||
private void resetForNextGesture() {
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
mTouchedView = null;
|
||||
mMovedEnough = false;
|
||||
mInDismissTarget = false;
|
||||
|
||||
mStack.onGestureFinished();
|
||||
}
|
||||
|
||||
@@ -25,13 +25,14 @@ import android.view.DisplayCutout;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.bubbles.BubbleExperimentConfig;
|
||||
import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
@@ -62,6 +63,12 @@ public class ExpandedAnimationController
|
||||
/** What percentage of the screen to use when centering the bubbles in landscape. */
|
||||
private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f;
|
||||
|
||||
/**
|
||||
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
|
||||
* target.
|
||||
*/
|
||||
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
|
||||
|
||||
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
|
||||
private float mStackOffsetPx;
|
||||
/** Space between status bar and bubbles in the expanded state. */
|
||||
@@ -79,9 +86,6 @@ public class ExpandedAnimationController
|
||||
/** What the current screen orientation is. */
|
||||
private int mScreenOrientation;
|
||||
|
||||
/** Whether the dragged-out bubble is in the dismiss target. */
|
||||
private boolean mIndividualBubbleWithinDismissTarget = false;
|
||||
|
||||
private boolean mAnimatingExpand = false;
|
||||
private boolean mAnimatingCollapse = false;
|
||||
private @Nullable Runnable mAfterExpand;
|
||||
@@ -99,6 +103,17 @@ public class ExpandedAnimationController
|
||||
*/
|
||||
private boolean mSpringingBubbleToTouch = false;
|
||||
|
||||
/**
|
||||
* Whether to spring the bubble to the next touch event coordinates. This is used to animate the
|
||||
* bubble out of the magnetic dismiss target to the touch location.
|
||||
*
|
||||
* Once it 'catches up' and the animation ends, we'll revert to moving it directly.
|
||||
*/
|
||||
private boolean mSpringToTouchOnNextMotionEvent = false;
|
||||
|
||||
/** The bubble currently being dragged out of the row (to potentially be dismissed). */
|
||||
private MagnetizedObject<View> mMagnetizedBubbleDraggingOut;
|
||||
|
||||
private int mExpandedViewPadding;
|
||||
|
||||
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
|
||||
@@ -113,9 +128,6 @@ public class ExpandedAnimationController
|
||||
*/
|
||||
private boolean mBubbleDraggedOutEnough = false;
|
||||
|
||||
/** The bubble currently being dragged out of the row (to potentially be dismissed). */
|
||||
private View mBubbleDraggingOut;
|
||||
|
||||
/**
|
||||
* Animates expanding the bubbles into a row along the top of the screen.
|
||||
*/
|
||||
@@ -235,12 +247,46 @@ public class ExpandedAnimationController
|
||||
}).startAll(after);
|
||||
}
|
||||
|
||||
/** Notifies the controller that the dragged-out bubble was unstuck from the magnetic target. */
|
||||
public void onUnstuckFromTarget() {
|
||||
mSpringToTouchOnNextMotionEvent = true;
|
||||
}
|
||||
|
||||
/** Prepares the given bubble to be dragged out. */
|
||||
public void prepareForBubbleDrag(View bubble) {
|
||||
public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) {
|
||||
mLayout.cancelAnimationsOnView(bubble);
|
||||
|
||||
mBubbleDraggingOut = bubble;
|
||||
mBubbleDraggingOut.setTranslationZ(Short.MAX_VALUE);
|
||||
bubble.setTranslationZ(Short.MAX_VALUE);
|
||||
mMagnetizedBubbleDraggingOut = new MagnetizedObject<View>(
|
||||
mLayout.getContext(), bubble,
|
||||
DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull View underlyingObject) {
|
||||
return mBubbleSizePx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(@NonNull View underlyingObject) {
|
||||
return mBubbleSizePx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
|
||||
loc[0] = (int) bubble.getTranslationX();
|
||||
loc[1] = (int) bubble.getTranslationY();
|
||||
}
|
||||
};
|
||||
mMagnetizedBubbleDraggingOut.addTarget(target);
|
||||
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
|
||||
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
|
||||
}
|
||||
|
||||
private void springBubbleTo(View bubble, float x, float y) {
|
||||
animationForChild(bubble)
|
||||
.translationX(x)
|
||||
.translationY(y)
|
||||
.withStiffness(SpringForce.STIFFNESS_HIGH)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,20 +295,20 @@ public class ExpandedAnimationController
|
||||
* bubble is dragged back into the row.
|
||||
*/
|
||||
public void dragBubbleOut(View bubbleView, float x, float y) {
|
||||
if (mSpringingBubbleToTouch) {
|
||||
if (mSpringToTouchOnNextMotionEvent) {
|
||||
springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
|
||||
mSpringToTouchOnNextMotionEvent = false;
|
||||
mSpringingBubbleToTouch = true;
|
||||
} else if (mSpringingBubbleToTouch) {
|
||||
if (mLayout.arePropertiesAnimatingOnView(
|
||||
bubbleView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)) {
|
||||
animationForChild(mBubbleDraggingOut)
|
||||
.translationX(x)
|
||||
.translationY(y)
|
||||
.withStiffness(SpringForce.STIFFNESS_HIGH)
|
||||
.start();
|
||||
springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
|
||||
} else {
|
||||
mSpringingBubbleToTouch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mSpringingBubbleToTouch && !mIndividualBubbleWithinDismissTarget) {
|
||||
if (!mSpringingBubbleToTouch && !mMagnetizedBubbleDraggingOut.getObjectStuckToTarget()) {
|
||||
bubbleView.setTranslationX(x);
|
||||
bubbleView.setTranslationY(y);
|
||||
}
|
||||
@@ -277,8 +323,6 @@ public class ExpandedAnimationController
|
||||
|
||||
/** Plays a dismiss animation on the dragged out bubble. */
|
||||
public void dismissDraggedOutBubble(View bubble, Runnable after) {
|
||||
mIndividualBubbleWithinDismissTarget = false;
|
||||
|
||||
animationForChild(bubble)
|
||||
.withStiffness(SpringForce.STIFFNESS_HIGH)
|
||||
.scaleX(1.1f)
|
||||
@@ -290,37 +334,14 @@ public class ExpandedAnimationController
|
||||
}
|
||||
|
||||
@Nullable public View getDraggedOutBubble() {
|
||||
return mBubbleDraggingOut;
|
||||
return mMagnetizedBubbleDraggingOut == null
|
||||
? null
|
||||
: mMagnetizedBubbleDraggingOut.getUnderlyingObject();
|
||||
}
|
||||
|
||||
/** Magnets the given bubble to the dismiss target. */
|
||||
public void magnetBubbleToDismiss(
|
||||
View bubbleView, float velX, float velY, float destY, Runnable after) {
|
||||
mIndividualBubbleWithinDismissTarget = true;
|
||||
mSpringingBubbleToTouch = false;
|
||||
animationForChild(bubbleView)
|
||||
.withStiffness(SpringForce.STIFFNESS_MEDIUM)
|
||||
.withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||
.withPositionStartVelocities(velX, velY)
|
||||
.translationX(mLayout.getWidth() / 2f - mBubbleSizePx / 2f)
|
||||
.translationY(destY, after)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Springs the dragged-out bubble towards the given coordinates and sets flags to have touch
|
||||
* events update the spring's final position until it's settled.
|
||||
*/
|
||||
public void demagnetizeBubbleTo(float x, float y, float velX, float velY) {
|
||||
mIndividualBubbleWithinDismissTarget = false;
|
||||
mSpringingBubbleToTouch = true;
|
||||
|
||||
animationForChild(mBubbleDraggingOut)
|
||||
.translationX(x)
|
||||
.translationY(y)
|
||||
.withPositionStartVelocities(velX, velY)
|
||||
.withStiffness(SpringForce.STIFFNESS_HIGH)
|
||||
.start();
|
||||
/** Returns the MagnetizedObject instance for the dragging-out bubble. */
|
||||
public MagnetizedObject<View> getMagnetizedBubbleDraggingOut() {
|
||||
return mMagnetizedBubbleDraggingOut;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,13 +356,14 @@ public class ExpandedAnimationController
|
||||
.withPositionStartVelocities(velX, velY)
|
||||
.start(() -> bubbleView.setTranslationZ(0f) /* after */);
|
||||
|
||||
mMagnetizedBubbleDraggingOut = null;
|
||||
|
||||
updateBubblePositions();
|
||||
}
|
||||
|
||||
/** Resets bubble drag out gesture flags. */
|
||||
public void onGestureFinished() {
|
||||
mBubbleDraggedOutEnough = false;
|
||||
mBubbleDraggingOut = null;
|
||||
updateBubblePositions();
|
||||
}
|
||||
|
||||
@@ -373,7 +395,6 @@ public class ExpandedAnimationController
|
||||
pw.print(" isActive: "); pw.println(isActiveController());
|
||||
pw.print(" animatingExpand: "); pw.println(mAnimatingExpand);
|
||||
pw.print(" animatingCollapse: "); pw.println(mAnimatingCollapse);
|
||||
pw.print(" bubbleInDismiss: "); pw.println(mIndividualBubbleWithinDismissTarget);
|
||||
pw.print(" springingBubble: "); pw.println(mSpringingBubbleToTouch);
|
||||
}
|
||||
|
||||
@@ -453,8 +474,8 @@ public class ExpandedAnimationController
|
||||
final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
|
||||
|
||||
// If we're removing the dragged-out bubble, that means it got dismissed.
|
||||
if (child.equals(mBubbleDraggingOut)) {
|
||||
mBubbleDraggingOut = null;
|
||||
if (child.equals(getDraggedOutBubble())) {
|
||||
mMagnetizedBubbleDraggingOut = null;
|
||||
finishRemoval.run();
|
||||
} else {
|
||||
animator.alpha(0f, finishRemoval /* endAction */)
|
||||
@@ -490,7 +511,7 @@ public class ExpandedAnimationController
|
||||
|
||||
// Don't animate the dragging out bubble, or it'll jump around while being dragged. It
|
||||
// will be snapped to the correct X value after the drag (if it's not dismissed).
|
||||
if (bubble.equals(mBubbleDraggingOut)) {
|
||||
if (bubble.equals(getDraggedOutBubble())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.systemui.bubbles.animation;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
@@ -25,6 +24,7 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FlingAnimation;
|
||||
@@ -35,6 +35,7 @@ import androidx.dynamicanimation.animation.SpringForce;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.util.FloatingContentCoordinator;
|
||||
import com.android.systemui.util.animation.PhysicsAnimator;
|
||||
import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
@@ -92,6 +93,9 @@ public class StackAnimationController extends
|
||||
*/
|
||||
private static final float ESCAPE_VELOCITY = 750f;
|
||||
|
||||
/** Velocity required to dismiss the stack without dragging it into the dismiss target. */
|
||||
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
|
||||
|
||||
/**
|
||||
* The canonical position of the stack. This is typically the position of the first bubble, but
|
||||
* we need to keep track of it separately from the first bubble's translation in case there are
|
||||
@@ -99,6 +103,12 @@ public class StackAnimationController extends
|
||||
*/
|
||||
private PointF mStackPosition = new PointF(-1, -1);
|
||||
|
||||
/**
|
||||
* MagnetizedObject instance for the stack, which is used by the touch handler for the magnetic
|
||||
* dismiss target.
|
||||
*/
|
||||
private MagnetizedObject<StackAnimationController> mMagnetizedStack;
|
||||
|
||||
/**
|
||||
* The area that Bubbles will occupy after all animations end. This is used to move other
|
||||
* floating content out of the way proactively.
|
||||
@@ -135,11 +145,6 @@ public class StackAnimationController extends
|
||||
*/
|
||||
private boolean mIsMovingFromFlinging = false;
|
||||
|
||||
/**
|
||||
* Whether the stack is within the dismiss target (either by being dragged, magnet'd, or flung).
|
||||
*/
|
||||
private boolean mWithinDismissTarget = false;
|
||||
|
||||
/**
|
||||
* Whether the first bubble is springing towards the touch point, rather than using the default
|
||||
* behavior of moving directly to the touch point with the rest of the stack following it.
|
||||
@@ -154,6 +159,14 @@ public class StackAnimationController extends
|
||||
*/
|
||||
private boolean mFirstBubbleSpringingToTouch = false;
|
||||
|
||||
/**
|
||||
* Whether to spring the stack to the next touch event coordinates. This is used to animate the
|
||||
* stack (including the first bubble) out of the magnetic dismiss target to the touch location.
|
||||
* Once it 'catches up' and the animation ends, we'll revert to moving the first bubble directly
|
||||
* and only animating the following bubbles.
|
||||
*/
|
||||
private boolean mSpringToTouchOnNextMotionEvent = false;
|
||||
|
||||
/** Horizontal offset of bubbles in the stack. */
|
||||
private float mStackOffset;
|
||||
/** Diameter of the bubble icon. */
|
||||
@@ -273,7 +286,8 @@ public class StackAnimationController extends
|
||||
* Note that we need new SpringForce instances per animation despite identical configs because
|
||||
* SpringAnimation uses SpringForce's internal (changing) velocity while the animation runs.
|
||||
*/
|
||||
public void springStack(float destinationX, float destinationY, float stiffness) {
|
||||
public void springStack(
|
||||
float destinationX, float destinationY, float stiffness) {
|
||||
notifyFloatingCoordinatorStackAnimatingTo(destinationX, destinationY);
|
||||
|
||||
springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
|
||||
@@ -404,7 +418,7 @@ public class StackAnimationController extends
|
||||
pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
|
||||
pw.print(" currentStackPos: "); pw.println(mStackPosition.toString());
|
||||
pw.print(" isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
|
||||
pw.print(" withinDismiss: "); pw.println(mWithinDismissTarget);
|
||||
pw.print(" withinDismiss: "); pw.println(isStackStuckToTarget());
|
||||
pw.print(" firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
|
||||
}
|
||||
|
||||
@@ -580,14 +594,18 @@ public class StackAnimationController extends
|
||||
|
||||
/** Moves the stack in response to a touch event. */
|
||||
public void moveStackFromTouch(float x, float y) {
|
||||
|
||||
// If we're springing to the touch point to 'catch up' after dragging out of the dismiss
|
||||
// target, then update the stack position animations instead of moving the bubble directly.
|
||||
if (mFirstBubbleSpringingToTouch) {
|
||||
// Begin the spring-to-touch catch up animation if needed.
|
||||
if (mSpringToTouchOnNextMotionEvent) {
|
||||
springStack(x, y, DEFAULT_STIFFNESS);
|
||||
mSpringToTouchOnNextMotionEvent = false;
|
||||
mFirstBubbleSpringingToTouch = true;
|
||||
} else if (mFirstBubbleSpringingToTouch) {
|
||||
final SpringAnimation springToTouchX =
|
||||
(SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_X);
|
||||
(SpringAnimation) mStackPositionAnimations.get(
|
||||
DynamicAnimation.TRANSLATION_X);
|
||||
final SpringAnimation springToTouchY =
|
||||
(SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_Y);
|
||||
(SpringAnimation) mStackPositionAnimations.get(
|
||||
DynamicAnimation.TRANSLATION_Y);
|
||||
|
||||
// If either animation is still running, we haven't caught up. Update the animations.
|
||||
if (springToTouchX.isRunning() || springToTouchY.isRunning()) {
|
||||
@@ -600,56 +618,14 @@ public class StackAnimationController extends
|
||||
}
|
||||
}
|
||||
|
||||
if (!mFirstBubbleSpringingToTouch && !mWithinDismissTarget) {
|
||||
if (!mFirstBubbleSpringingToTouch && !isStackStuckToTarget()) {
|
||||
moveFirstBubbleWithStackFollowing(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Demagnetizes the stack, springing it towards the given point. This also sets flags so that
|
||||
* subsequent touch events will update the final position of the demagnetization spring instead
|
||||
* of directly moving the bubbles, until demagnetization is complete.
|
||||
*/
|
||||
public void demagnetizeFromDismissToPoint(float x, float y, float velX, float velY) {
|
||||
mWithinDismissTarget = false;
|
||||
mFirstBubbleSpringingToTouch = true;
|
||||
|
||||
springFirstBubbleWithStackFollowing(
|
||||
DynamicAnimation.TRANSLATION_X,
|
||||
new SpringForce()
|
||||
.setDampingRatio(DEFAULT_BOUNCINESS)
|
||||
.setStiffness(DEFAULT_STIFFNESS),
|
||||
velX, x);
|
||||
|
||||
springFirstBubbleWithStackFollowing(
|
||||
DynamicAnimation.TRANSLATION_Y,
|
||||
new SpringForce()
|
||||
.setDampingRatio(DEFAULT_BOUNCINESS)
|
||||
.setStiffness(DEFAULT_STIFFNESS),
|
||||
velY, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring the stack towards the dismiss target, respecting existing velocity. This also sets
|
||||
* flags so that subsequent touch events will not move the stack until it's demagnetized.
|
||||
*/
|
||||
public void magnetToDismiss(float velX, float velY, float destY, Runnable after) {
|
||||
mWithinDismissTarget = true;
|
||||
mFirstBubbleSpringingToTouch = false;
|
||||
|
||||
springFirstBubbleWithStackFollowing(
|
||||
DynamicAnimation.TRANSLATION_X,
|
||||
new SpringForce()
|
||||
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||
.setStiffness(SpringForce.STIFFNESS_MEDIUM),
|
||||
velX, mLayout.getWidth() / 2f - mBubbleBitmapSize / 2f);
|
||||
|
||||
springFirstBubbleWithStackFollowing(
|
||||
DynamicAnimation.TRANSLATION_Y,
|
||||
new SpringForce()
|
||||
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||
.setStiffness(SpringForce.STIFFNESS_MEDIUM),
|
||||
velY, destY, after);
|
||||
/** Notify the controller that the stack has been unstuck from the dismiss target. */
|
||||
public void onUnstuckFromTarget() {
|
||||
mSpringToTouchOnNextMotionEvent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -663,13 +639,7 @@ public class StackAnimationController extends
|
||||
.alpha(0f)
|
||||
.withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
|
||||
.withStiffness(SpringForce.STIFFNESS_HIGH)
|
||||
.start(() -> {
|
||||
// Run the callback and reset flags. The child translation animations might
|
||||
// still be running, but that's fine. Once the alpha is at 0f they're no longer
|
||||
// visible anyway.
|
||||
after.run();
|
||||
mWithinDismissTarget = false;
|
||||
});
|
||||
.start(after);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -720,7 +690,7 @@ public class StackAnimationController extends
|
||||
if (property.equals(DynamicAnimation.TRANSLATION_X)
|
||||
|| property.equals(DynamicAnimation.TRANSLATION_Y)) {
|
||||
return index + 1;
|
||||
} else if (mWithinDismissTarget) {
|
||||
} else if (isStackStuckToTarget()) {
|
||||
return index + 1; // Chain all animations in dismiss (scale, alpha, etc. are used).
|
||||
} else {
|
||||
return NONE;
|
||||
@@ -733,7 +703,7 @@ public class StackAnimationController extends
|
||||
if (property.equals(DynamicAnimation.TRANSLATION_X)) {
|
||||
// If we're in the dismiss target, have the bubbles pile on top of each other with no
|
||||
// offset.
|
||||
if (mWithinDismissTarget) {
|
||||
if (isStackStuckToTarget()) {
|
||||
return 0f;
|
||||
} else {
|
||||
// Offset to the left if we're on the left, or the right otherwise.
|
||||
@@ -755,7 +725,7 @@ public class StackAnimationController extends
|
||||
@Override
|
||||
void onChildAdded(View child, int index) {
|
||||
// Don't animate additions within the dismiss target.
|
||||
if (mWithinDismissTarget) {
|
||||
if (isStackStuckToTarget()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -784,8 +754,6 @@ public class StackAnimationController extends
|
||||
if (mLayout.getChildCount() > 0) {
|
||||
animationForChildAtIndex(0).translationX(mStackPosition.x).start();
|
||||
} else {
|
||||
// If there's no other bubbles, and we were in the dismiss target, reset the flag.
|
||||
mWithinDismissTarget = false;
|
||||
// When all children are removed ensure stack position is sane
|
||||
setStackPosition(mRestingStackPosition == null
|
||||
? getDefaultStartPosition()
|
||||
@@ -831,6 +799,9 @@ public class StackAnimationController extends
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStackStuckToTarget() {
|
||||
return mMagnetizedStack != null && mMagnetizedStack.getObjectStuckToTarget();
|
||||
}
|
||||
|
||||
/** Moves the stack, without any animation, to the starting position. */
|
||||
private void moveStackToStartPosition() {
|
||||
@@ -958,6 +929,44 @@ public class StackAnimationController extends
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
|
||||
* {@link MagnetizedObject.MagneticTarget} added as a target.
|
||||
*/
|
||||
public MagnetizedObject<StackAnimationController> getMagnetizedStack(
|
||||
MagnetizedObject.MagneticTarget target) {
|
||||
if (mMagnetizedStack == null) {
|
||||
mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
|
||||
mLayout.getContext(),
|
||||
this,
|
||||
new StackPositionProperty(DynamicAnimation.TRANSLATION_X),
|
||||
new StackPositionProperty(DynamicAnimation.TRANSLATION_Y)
|
||||
) {
|
||||
@Override
|
||||
public float getWidth(@NonNull StackAnimationController underlyingObject) {
|
||||
return mBubbleSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(@NonNull StackAnimationController underlyingObject) {
|
||||
return mBubbleSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocationOnScreen(@NonNull StackAnimationController underlyingObject,
|
||||
@NonNull int[] loc) {
|
||||
loc[0] = (int) mStackPosition.x;
|
||||
loc[1] = (int) mStackPosition.y;
|
||||
}
|
||||
};
|
||||
mMagnetizedStack.addTarget(target);
|
||||
mMagnetizedStack.setHapticsEnabled(true);
|
||||
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
|
||||
}
|
||||
|
||||
return mMagnetizedStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
|
||||
* translation and animate the rest of the stack with it. A DynamicAnimation can animate this
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.view.Gravity;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
/**
|
||||
* Circular view with a semitransparent, circular background with an 'X' inside it.
|
||||
*
|
||||
* This is used by both Bubbles and PIP as the dismiss target.
|
||||
*/
|
||||
public class DismissCircleView extends FrameLayout {
|
||||
|
||||
private final ImageView mIconView = new ImageView(getContext());
|
||||
|
||||
public DismissCircleView(Context context) {
|
||||
super(context);
|
||||
final Resources res = getResources();
|
||||
|
||||
setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
|
||||
|
||||
mIconView.setImageDrawable(res.getDrawable(R.drawable.dismiss_target_x));
|
||||
addView(mIconView);
|
||||
|
||||
setViewSizes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
setViewSizes();
|
||||
}
|
||||
|
||||
/** Retrieves the current dimensions for the icon and circle and applies them. */
|
||||
private void setViewSizes() {
|
||||
final Resources res = getResources();
|
||||
final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
|
||||
mIconView.setLayoutParams(
|
||||
new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.systemui.bubbles.animation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
@@ -118,118 +117,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
|
||||
testBubblesInCorrectExpandedPositions();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException {
|
||||
expand();
|
||||
|
||||
final View draggedBubble = mViews.get(0);
|
||||
mExpandedController.prepareForBubbleDrag(draggedBubble);
|
||||
mExpandedController.dragBubbleOut(draggedBubble, 500f, 500f);
|
||||
|
||||
assertEquals(500f, draggedBubble.getTranslationX(), 1f);
|
||||
assertEquals(500f, draggedBubble.getTranslationY(), 1f);
|
||||
|
||||
// Snap it back and make sure it made it back correctly.
|
||||
mExpandedController.snapBubbleBack(draggedBubble, 0f, 0f);
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
testBubblesInCorrectExpandedPositions();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testBubbleDismissed() throws InterruptedException {
|
||||
expand();
|
||||
|
||||
final View draggedBubble = mViews.get(0);
|
||||
mExpandedController.prepareForBubbleDrag(draggedBubble);
|
||||
mExpandedController.dragBubbleOut(draggedBubble, 500f, 500f);
|
||||
|
||||
assertEquals(500f, draggedBubble.getTranslationX(), 1f);
|
||||
assertEquals(500f, draggedBubble.getTranslationY(), 1f);
|
||||
|
||||
mLayout.removeView(draggedBubble);
|
||||
waitForLayoutMessageQueue();
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
|
||||
assertEquals(-1, mLayout.indexOfChild(draggedBubble));
|
||||
testBubblesInCorrectExpandedPositions();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Flaky")
|
||||
public void testMagnetToDismiss_dismiss() throws InterruptedException {
|
||||
expand();
|
||||
|
||||
final View draggedOutView = mViews.get(0);
|
||||
final Runnable after = Mockito.mock(Runnable.class);
|
||||
|
||||
mExpandedController.prepareForBubbleDrag(draggedOutView);
|
||||
mExpandedController.dragBubbleOut(draggedOutView, 25, 25);
|
||||
|
||||
// Magnet to dismiss, verify the bubble is at the dismiss target and the callback was
|
||||
// called.
|
||||
mExpandedController.magnetBubbleToDismiss(
|
||||
mViews.get(0), 100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
verify(after).run();
|
||||
assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
|
||||
|
||||
// Dismiss the now-magneted bubble, verify that the callback was called.
|
||||
final Runnable afterDismiss = Mockito.mock(Runnable.class);
|
||||
mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss);
|
||||
waitForPropertyAnimations(DynamicAnimation.ALPHA);
|
||||
verify(after).run();
|
||||
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
|
||||
assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Flaky")
|
||||
public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
|
||||
expand();
|
||||
|
||||
final View draggedOutView = mViews.get(0);
|
||||
final Runnable after = Mockito.mock(Runnable.class);
|
||||
|
||||
mExpandedController.prepareForBubbleDrag(draggedOutView);
|
||||
mExpandedController.dragBubbleOut(draggedOutView, 25, 25);
|
||||
|
||||
// Magnet to dismiss, verify the bubble is at the dismiss target and the callback was
|
||||
// called.
|
||||
mExpandedController.magnetBubbleToDismiss(
|
||||
draggedOutView, 100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
verify(after).run();
|
||||
assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
|
||||
|
||||
// Demagnetize the bubble towards (25, 25).
|
||||
mExpandedController.demagnetizeBubbleTo(25 /* x */, 25 /* y */, 100, 100);
|
||||
|
||||
// Start dragging towards (20, 20).
|
||||
mExpandedController.dragBubbleOut(draggedOutView, 20, 20);
|
||||
|
||||
// Since we just demagnetized, the bubble shouldn't be at (20, 20), it should be animating
|
||||
// towards it.
|
||||
assertNotEquals(20, draggedOutView.getTranslationX());
|
||||
assertNotEquals(20, draggedOutView.getTranslationY());
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
|
||||
// Waiting for the animations should result in the bubble ending at (20, 20) since the
|
||||
// animation end value was updated.
|
||||
assertEquals(20, draggedOutView.getTranslationX(), 1f);
|
||||
assertEquals(20, draggedOutView.getTranslationY(), 1f);
|
||||
|
||||
// Drag to (30, 30).
|
||||
mExpandedController.dragBubbleOut(draggedOutView, 30, 30);
|
||||
|
||||
// It should go there instantly since the animations finished.
|
||||
assertEquals(30, draggedOutView.getTranslationX(), 1f);
|
||||
assertEquals(30, draggedOutView.getTranslationY(), 1f);
|
||||
}
|
||||
|
||||
/** Expand the stack and wait for animations to finish. */
|
||||
private void expand() throws InterruptedException {
|
||||
mExpandedController.expandFromStack(Mockito.mock(Runnable.class));
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.systemui.bubbles.animation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -41,7 +40,6 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -241,61 +239,6 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
|
||||
assertEquals(prevStackPos, mStackController.getStackPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Flaky")
|
||||
public void testMagnetToDismiss_dismiss() throws InterruptedException {
|
||||
final Runnable after = Mockito.mock(Runnable.class);
|
||||
|
||||
// Magnet to dismiss, verify the stack is at the dismiss target and the callback was
|
||||
// called.
|
||||
mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
verify(after).run();
|
||||
assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
|
||||
|
||||
// Dismiss the stack, verify that the callback was called.
|
||||
final Runnable afterImplode = Mockito.mock(Runnable.class);
|
||||
mStackController.implodeStack(afterImplode);
|
||||
waitForPropertyAnimations(
|
||||
DynamicAnimation.ALPHA, DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y);
|
||||
verify(after).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Flaking")
|
||||
public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
|
||||
final Runnable after = Mockito.mock(Runnable.class);
|
||||
|
||||
// Magnet to dismiss, verify the stack is at the dismiss target and the callback was
|
||||
// called.
|
||||
mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
verify(after).run();
|
||||
|
||||
assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
|
||||
|
||||
// Demagnetize towards (25, 25) and then send a touch event.
|
||||
mStackController.demagnetizeFromDismissToPoint(25, 25, 0, 0);
|
||||
waitForLayoutMessageQueue();
|
||||
mStackController.moveStackFromTouch(20, 20);
|
||||
|
||||
// Since the stack is demagnetizing, it shouldn't be at the stack position yet.
|
||||
assertNotEquals(20, mStackController.getStackPosition().x, 1f);
|
||||
assertNotEquals(20, mStackController.getStackPosition().y, 1f);
|
||||
|
||||
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
|
||||
|
||||
// Once the animation is done it should end at the touch position coordinates.
|
||||
assertEquals(20, mStackController.getStackPosition().x, 1f);
|
||||
assertEquals(20, mStackController.getStackPosition().y, 1f);
|
||||
|
||||
mStackController.moveStackFromTouch(30, 30);
|
||||
|
||||
// Touches after the animation are done should change the stack position instantly.
|
||||
assertEquals(30, mStackController.getStackPosition().x, 1f);
|
||||
assertEquals(30, mStackController.getStackPosition().y, 1f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFloatingCoordinator() {
|
||||
// We should have called onContentAdded only once while adding all of the bubbles in
|
||||
|
||||
Reference in New Issue
Block a user