Merge "Bubble bar dismiss interaction" into udc-qpr-dev am: 2de68329fd
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/24209849 Change-Id: I091aeba4f3bb0d60e23ac79b8f9abf0765e26b09 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -49,6 +49,7 @@
|
||||
android:visibility="gone"
|
||||
android:gravity="center"
|
||||
android:clipChildren="false"
|
||||
android:elevation="@dimen/bubblebar_elevation"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
<dimen name="bubblebar_stashed_size">@dimen/transient_taskbar_stashed_height</dimen>
|
||||
<dimen name="bubblebar_stashed_handle_height">@dimen/taskbar_stashed_handle_height</dimen>
|
||||
<dimen name="bubblebar_pointer_size">8dp</dimen>
|
||||
<dimen name="bubblebar_elevation">1dp</dimen>
|
||||
|
||||
<dimen name="bubblebar_icon_size">50dp</dimen>
|
||||
<dimen name="bubblebar_badge_size">24dp</dimen>
|
||||
|
||||
@@ -90,6 +90,8 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
|
||||
@@ -216,7 +218,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
new BubbleBarController(this, bubbleBarView),
|
||||
new BubbleBarViewController(this, bubbleBarView),
|
||||
new BubbleStashController(this),
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView)));
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView),
|
||||
new BubbleDragController(this),
|
||||
new BubbleDismissController(this, mDragLayer)));
|
||||
}
|
||||
|
||||
// Construct controllers.
|
||||
|
||||
@@ -95,6 +95,8 @@ public class BubbleBarView extends FrameLayout {
|
||||
private View.OnClickListener mOnClickListener;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
private float mRelativePivotX = 1f;
|
||||
private float mRelativePivotY = 1f;
|
||||
|
||||
// An animator that represents the expansion state of the bubble bar, where 0 corresponds to the
|
||||
// collapsed state and 1 to the fully expanded state.
|
||||
@@ -109,6 +111,9 @@ public class BubbleBarView extends FrameLayout {
|
||||
@Nullable
|
||||
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
|
||||
|
||||
@Nullable
|
||||
private BubbleView mDraggedBubbleView;
|
||||
|
||||
public BubbleBarView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -181,9 +186,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleBarBounds.right = right;
|
||||
mBubbleBarBounds.bottom = bottom;
|
||||
|
||||
// The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
|
||||
setPivotX(getWidth());
|
||||
setPivotY(getHeight());
|
||||
// The bubble bar handle is aligned according to the relative pivot,
|
||||
// by default it's aligned to the bottom edge of the screen so scale towards that
|
||||
setPivotX(mRelativePivotX * getWidth());
|
||||
setPivotY(mRelativePivotY * getHeight());
|
||||
|
||||
// Position the views
|
||||
updateChildrenRenderNodeProperties();
|
||||
@@ -198,6 +204,32 @@ public class BubbleBarView extends FrameLayout {
|
||||
return mBubbleBarBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
|
||||
* respectively. If the value is not in range of 0 to 1 it will be normalized.
|
||||
* @param x relative X pivot value in range 0..1
|
||||
* @param y relative Y pivot value in range 0..1
|
||||
*/
|
||||
public void setRelativePivot(float x, float y) {
|
||||
mRelativePivotX = Float.max(Float.min(x, 1), 0);
|
||||
mRelativePivotY = Float.max(Float.min(y, 1), 0);
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current relative pivot for X axis
|
||||
*/
|
||||
public float getRelativePivotX() {
|
||||
return mRelativePivotX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current relative pivot for Y axis
|
||||
*/
|
||||
public float getRelativePivotY() {
|
||||
return mRelativePivotY;
|
||||
}
|
||||
|
||||
// TODO: (b/280605790) animate it
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
@@ -254,9 +286,9 @@ public class BubbleBarView extends FrameLayout {
|
||||
// where the bubble will end up when the animation ends
|
||||
final float targetX = currentWidth - expandedWidth + expandedX;
|
||||
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
|
||||
// if we're fully expanded, set the z level to 0
|
||||
// if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
|
||||
if (widthState == 1f) {
|
||||
bv.setZ(0);
|
||||
bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
|
||||
}
|
||||
// When we're expanded, we're not stacked so we're not behind the stack
|
||||
bv.setBehindStack(false, animate);
|
||||
@@ -331,6 +363,14 @@ public class BubbleBarView extends FrameLayout {
|
||||
updateArrowForSelected(/* shouldAnimate= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
|
||||
*/
|
||||
public void setDraggedBubble(@Nullable BubbleView view) {
|
||||
mDraggedBubbleView = view;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the arrow position to match the selected bubble.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
@@ -54,6 +56,7 @@ public class BubbleBarViewController {
|
||||
// Initialized in init.
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private BubbleBarController mBubbleBarController;
|
||||
private BubbleDragController mBubbleDragController;
|
||||
private TaskbarStashController mTaskbarStashController;
|
||||
private TaskbarInsetsController mTaskbarInsetsController;
|
||||
private View.OnClickListener mBubbleClickListener;
|
||||
@@ -85,6 +88,7 @@ public class BubbleBarViewController {
|
||||
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
|
||||
mBubbleStashController = bubbleControllers.bubbleStashController;
|
||||
mBubbleBarController = bubbleControllers.bubbleBarController;
|
||||
mBubbleDragController = bubbleControllers.bubbleDragController;
|
||||
mTaskbarStashController = controllers.taskbarStashController;
|
||||
mTaskbarInsetsController = controllers.taskbarInsetsController;
|
||||
|
||||
@@ -95,6 +99,7 @@ public class BubbleBarViewController {
|
||||
mBubbleBarScale.updateValue(1f);
|
||||
mBubbleClickListener = v -> onBubbleClicked(v);
|
||||
mBubbleBarClickListener = v -> setExpanded(true);
|
||||
mBubbleDragController.setupBubbleBarView(mBarView);
|
||||
mBarView.setOnClickListener(mBubbleBarClickListener);
|
||||
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
|
||||
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
|
||||
@@ -268,6 +273,7 @@ public class BubbleBarViewController {
|
||||
if (b != null) {
|
||||
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
|
||||
b.getView().setOnClickListener(mBubbleClickListener);
|
||||
mBubbleDragController.setupBubbleView(b.getView());
|
||||
} else {
|
||||
Log.w(TAG, "addBubble, bubble was null!");
|
||||
}
|
||||
@@ -319,4 +325,46 @@ public class BubbleBarViewController {
|
||||
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
|
||||
* that a bubble is being dragged to dismiss.
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragStart(@NonNull BubbleView bubbleView) {
|
||||
if (bubbleView.getBubble() == null) return;
|
||||
mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
|
||||
mBarView.setDraggedBubble(bubbleView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies SystemUI to expand the selected bubble when the bubble is released.
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragRelease(@NonNull BubbleView bubbleView) {
|
||||
if (bubbleView.getBubble() == null) return;
|
||||
mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the dragged bubble view in the bubble bar view
|
||||
*/
|
||||
public void onDragEnd() {
|
||||
mBarView.setDraggedBubble(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble was dragged into the dismiss target. Notifies System
|
||||
* @param bubble dismissed bubble item
|
||||
*/
|
||||
public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
|
||||
mSystemUiProxy.removeBubble(bubble.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble stack was dragged into the dismiss target
|
||||
*/
|
||||
public void onDismissAllBubblesWhileDragging() {
|
||||
mSystemUiProxy.removeAllBubbles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public class BubbleControllers {
|
||||
public final BubbleBarViewController bubbleBarViewController;
|
||||
public final BubbleStashController bubbleStashController;
|
||||
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
|
||||
public final BubbleDragController bubbleDragController;
|
||||
public final BubbleDismissController bubbleDismissController;
|
||||
|
||||
private final RunnableList mPostInitRunnables = new RunnableList();
|
||||
|
||||
@@ -39,11 +41,15 @@ public class BubbleControllers {
|
||||
BubbleBarController bubbleBarController,
|
||||
BubbleBarViewController bubbleBarViewController,
|
||||
BubbleStashController bubbleStashController,
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController) {
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController,
|
||||
BubbleDragController bubbleDragController,
|
||||
BubbleDismissController bubbleDismissController) {
|
||||
this.bubbleBarController = bubbleBarController;
|
||||
this.bubbleBarViewController = bubbleBarViewController;
|
||||
this.bubbleStashController = bubbleStashController;
|
||||
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
|
||||
this.bubbleDragController = bubbleDragController;
|
||||
this.bubbleDismissController = bubbleDismissController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,6 +62,8 @@ public class BubbleControllers {
|
||||
bubbleBarViewController.init(taskbarControllers, this);
|
||||
bubbleStashedHandleViewController.init(taskbarControllers, this);
|
||||
bubbleStashController.init(taskbarControllers, this);
|
||||
bubbleDragController.init(/* bubbleControllers = */ this);
|
||||
bubbleDismissController.init(/* bubbleControllers = */ this);
|
||||
|
||||
mPostInitRunnables.executeAllAndDestroy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.launcher3.taskbar.bubbles;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarDragLayer;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
/**
|
||||
* Controls dismiss view presentation for the bubble bar dismiss functionality.
|
||||
* Provides the dragged view snapping to the target dismiss area and animates it.
|
||||
* When the dragged bubble/bubble stack is released inside of the target area, it gets dismissed.
|
||||
*
|
||||
* @see BubbleDragController
|
||||
*/
|
||||
public class BubbleDismissController {
|
||||
private static final String TAG = BubbleDismissController.class.getSimpleName();
|
||||
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
|
||||
// LINT.IfChange
|
||||
private static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
|
||||
SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
|
||||
// LINT.ThenChange(com/android/wm/shell/bubbles/BubbleStackView.java)
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mDragLayer;
|
||||
@Nullable
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
|
||||
// Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
|
||||
// dismiss view used as a dismiss target.
|
||||
@Nullable
|
||||
private DismissView mDismissView;
|
||||
|
||||
// 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.
|
||||
@Nullable
|
||||
private MagnetizedObject<View> mMagnetizedObject;
|
||||
|
||||
// The MagneticTarget instance for our circular dismiss view. This is added to the
|
||||
// MagnetizedObject instances for the stack and any dragged-out bubbles.
|
||||
@Nullable
|
||||
private MagnetizedObject.MagneticTarget mMagneticTarget;
|
||||
|
||||
// The bubble drag animator that synchronizes bubble drag and dismiss view animations
|
||||
// A new instance is provided when the dismiss view is setup
|
||||
@Nullable
|
||||
private BubbleDragAnimator mAnimator;
|
||||
|
||||
public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
|
||||
mActivity = activity;
|
||||
mDragLayer = dragLayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dependencies when bubble controllers are created.
|
||||
* Should be careful to only access things that were created in constructors for now, as some
|
||||
* controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the dismiss view and magnetized object that will be attracted to magnetic target.
|
||||
* Should be called before handling events or showing/hiding dismiss view.
|
||||
*
|
||||
* @param magnetizedView the view to be pulled into target dismiss area
|
||||
* @param animator the bubble animator to be used for the magnetized view, it syncs bubble
|
||||
* dragging and dismiss animations with the dismiss view provided.
|
||||
*/
|
||||
public void setupDismissView(@NonNull View magnetizedView,
|
||||
@NonNull BubbleDragAnimator animator) {
|
||||
setupDismissView();
|
||||
setupMagnetizedObject(magnetizedView);
|
||||
if (mDismissView != null) {
|
||||
animator.setDismissView(mDismissView);
|
||||
mAnimator = animator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the touch event and pass it to the magnetized object.
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public boolean handleTouchEvent(@NonNull MotionEvent event) {
|
||||
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dismiss view with animation
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public void showDismissView() {
|
||||
if (mDismissView == null) return;
|
||||
mDismissView.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide dismiss view with animation
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public void hideDismissView() {
|
||||
if (mDismissView == null) return;
|
||||
mDismissView.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss magnetized object when it's released in the dismiss target area
|
||||
*/
|
||||
private void dismissMagnetizedObject() {
|
||||
if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
|
||||
if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
|
||||
BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
|
||||
if (bubbleView.getBubble() != null) {
|
||||
mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
|
||||
}
|
||||
} else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
|
||||
mBubbleBarViewController.onDismissAllBubblesWhileDragging();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDismissView() {
|
||||
if (mDismissView != null) return;
|
||||
mDismissView = new DismissView(mActivity.getApplicationContext());
|
||||
BubbleDismissViewUtils.setup(mDismissView);
|
||||
mDragLayer.addView(mDismissView, /* index = */ 0,
|
||||
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
mDismissView.setElevation(mDismissView.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_elevation));
|
||||
setupMagneticTarget(mDismissView.getCircle());
|
||||
}
|
||||
|
||||
private void setupMagneticTarget(@NonNull View view) {
|
||||
int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_size);
|
||||
mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
|
||||
}
|
||||
|
||||
private void setupMagnetizedObject(@NonNull View magnetizedView) {
|
||||
mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
|
||||
magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull View underlyingObject) {
|
||||
return underlyingObject.getWidth() * underlyingObject.getScaleX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(@NonNull View underlyingObject) {
|
||||
return underlyingObject.getHeight() * underlyingObject.getScaleY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
|
||||
underlyingObject.getLocationOnScreen(loc);
|
||||
}
|
||||
};
|
||||
|
||||
mMagnetizedObject.setHapticsEnabled(true);
|
||||
mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
|
||||
mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
|
||||
if (mMagneticTarget != null) {
|
||||
mMagnetizedObject.addTarget(mMagneticTarget);
|
||||
} else {
|
||||
Log.e(TAG,"Requires MagneticTarget to add target to MagnetizedObject!");
|
||||
}
|
||||
mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
if (mAnimator == null) return;
|
||||
mAnimator.animateDismissCaptured();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
if (mAnimator == null) return;
|
||||
mAnimator.animateDismissReleased();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
dismissMagnetizedObject();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.launcher3.taskbar.bubbles;
|
||||
|
||||
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
|
||||
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
|
||||
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.wm.shell.animation.PhysicsAnimator;
|
||||
import com.android.wm.shell.common.bubbles.DismissCircleView;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
|
||||
/**
|
||||
* The animator performs the bubble animations while dragging and coordinates bubble and dismiss
|
||||
* view animations when it gets magnetized, released or dismissed.
|
||||
*/
|
||||
public class BubbleDragAnimator {
|
||||
private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
|
||||
private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
|
||||
private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
|
||||
|
||||
private final PhysicsAnimator.SpringConfig mDefaultConfig =
|
||||
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
|
||||
private final PhysicsAnimator.SpringConfig mTranslationConfig =
|
||||
new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
|
||||
@NonNull
|
||||
private final View mView;
|
||||
@NonNull
|
||||
private final PhysicsAnimator<View> mBubbleAnimator;
|
||||
@Nullable
|
||||
private DismissView mDismissView;
|
||||
@Nullable
|
||||
private PhysicsAnimator<DismissCircleView> mDismissAnimator;
|
||||
private final float mBubbleFocusedScale;
|
||||
private final float mBubbleCapturedScale;
|
||||
private final float mDismissCapturedScale;
|
||||
|
||||
/**
|
||||
* Should be initialised for each dragged view
|
||||
*
|
||||
* @param view the dragged view to animate
|
||||
*/
|
||||
public BubbleDragAnimator(@NonNull View view) {
|
||||
mView = view;
|
||||
mBubbleAnimator = PhysicsAnimator.getInstance(view);
|
||||
mBubbleAnimator.setDefaultSpringConfig(mDefaultConfig);
|
||||
|
||||
Resources resources = view.getResources();
|
||||
final int collapsedSize = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_small_size);
|
||||
final int expandedSize = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_size);
|
||||
mDismissCapturedScale = (float) collapsedSize / expandedSize;
|
||||
|
||||
if (view instanceof BubbleBarView) {
|
||||
mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
|
||||
mBubbleCapturedScale = mDismissCapturedScale;
|
||||
} else {
|
||||
mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
|
||||
mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets dismiss view to be animated alongside the dragged bubble
|
||||
*/
|
||||
public void setDismissView(@NonNull DismissView dismissView) {
|
||||
mDismissView = dismissView;
|
||||
mDismissAnimator = PhysicsAnimator.getInstance(dismissView.getCircle());
|
||||
mDismissAnimator.setDefaultSpringConfig(mDefaultConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the focused state of the bubble when the dragging starts
|
||||
*/
|
||||
public void animateFocused() {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged bubble movement back to the initial position.
|
||||
*
|
||||
* @param initialPosition the position to animate to
|
||||
* @param velocity the initial velocity to use for the spring animation
|
||||
* @param endActions gets called when the animation completes or gets cancelled
|
||||
*/
|
||||
public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
|
||||
@Nullable Runnable endActions) {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, 1f)
|
||||
.spring(DynamicAnimation.SCALE_Y, 1f)
|
||||
.spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
|
||||
mTranslationConfig)
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
|
||||
mTranslationConfig)
|
||||
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
|
||||
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
|
||||
boolean allRelevantPropertyAnimationsEnded) -> {
|
||||
if (canceled || allRelevantPropertyAnimationsEnded) {
|
||||
resetAnimatedViews(initialPosition);
|
||||
if (endActions != null) {
|
||||
endActions.run();
|
||||
}
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
|
||||
* target area.
|
||||
*/
|
||||
public void animateDismissCaptured() {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mBubbleCapturedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mBubbleCapturedScale)
|
||||
.spring(DynamicAnimation.ALPHA, mDismissCapturedScale)
|
||||
.start();
|
||||
|
||||
if (mDismissAnimator != null) {
|
||||
mDismissAnimator.cancel();
|
||||
mDismissAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mDismissCapturedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mDismissCapturedScale)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged view alongside the dismiss view when it gets released from the dismiss
|
||||
* target area.
|
||||
*/
|
||||
public void animateDismissReleased() {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
|
||||
.spring(DynamicAnimation.ALPHA, 1f)
|
||||
.start();
|
||||
|
||||
if (mDismissAnimator != null) {
|
||||
mDismissAnimator.cancel();
|
||||
mDismissAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, 1f)
|
||||
.spring(DynamicAnimation.SCALE_Y, 1f)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged bubble dismiss when it's released in the dismiss target area.
|
||||
*
|
||||
* @param initialPosition the initial position to move the bubble too after animation finishes
|
||||
* @param endActions gets called when the animation completes or gets cancelled
|
||||
*/
|
||||
public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
|
||||
float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
|
||||
float translationY = mView.getTranslationY() + dismissHeight;
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, translationY)
|
||||
.spring(DynamicAnimation.SCALE_X, 0f)
|
||||
.spring(DynamicAnimation.SCALE_Y, 0f)
|
||||
.spring(DynamicAnimation.ALPHA, 0f)
|
||||
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
|
||||
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
|
||||
boolean allRelevantPropertyAnimationsEnded) -> {
|
||||
if (canceled || allRelevantPropertyAnimationsEnded) {
|
||||
resetAnimatedViews(initialPosition);
|
||||
if (endActions != null) endActions.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the animated views to the initial state
|
||||
*
|
||||
* @param initialPosition position of the bubble
|
||||
*/
|
||||
private void resetAnimatedViews(@NonNull PointF initialPosition) {
|
||||
mView.setScaleX(1f);
|
||||
mView.setScaleY(1f);
|
||||
mView.setAlpha(1f);
|
||||
mView.setTranslationX(initialPosition.x);
|
||||
mView.setTranslationY(initialPosition.y);
|
||||
|
||||
if (mDismissView != null) {
|
||||
mDismissView.getCircle().setScaleX(1f);
|
||||
mDismissView.getCircle().setScaleY(1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.launcher3.taskbar.bubbles;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
|
||||
/**
|
||||
* Controls bubble bar drag to dismiss interaction.
|
||||
* Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
|
||||
* Supported interactions:
|
||||
* - Drag a single bubble view into dismiss target to remove it.
|
||||
* - Drag the bubble stack into dismiss target to remove all.
|
||||
* Restores initial position of dragged view if released outside of the dismiss target.
|
||||
*/
|
||||
public class BubbleDragController {
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleDismissController mBubbleDismissController;
|
||||
|
||||
public BubbleDragController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dependencies when bubble controllers are created.
|
||||
* Should be careful to only access things that were created in constructors for now, as some
|
||||
* controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleDismissController = bubbleControllers.bubbleDismissController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the bubble view for dragging and attach touch listener to it
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleView(@NonNull BubbleView bubbleView) {
|
||||
if (!(bubbleView.getBubble() instanceof BubbleBarBubble)) {
|
||||
// Don't setup dragging for overflow bubble view
|
||||
return;
|
||||
}
|
||||
|
||||
bubbleView.setOnTouchListener(new BubbleTouchListener() {
|
||||
@Override
|
||||
void onDragStart() {
|
||||
mBubbleBarViewController.onDragStart(bubbleView);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
mBubbleBarViewController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragRelease() {
|
||||
mBubbleBarViewController.onDragRelease(bubbleView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the bubble bar view for dragging and attach touch listener to it
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
|
||||
PointF initialRelativePivot = new PointF();
|
||||
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
|
||||
@Override
|
||||
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
if (bubbleBarView.isExpanded()) return false;
|
||||
return super.onTouchDown(view, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragStart() {
|
||||
initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
|
||||
bubbleBarView.getRelativePivotY());
|
||||
// By default the bubble bar view pivot is in bottom right corner, while dragging
|
||||
// it should be centered in order to align it with the dismiss target view
|
||||
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
// Restoring the initial pivot for the bubble bar view
|
||||
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble touch listener for handling a single bubble view or bubble bar view while dragging.
|
||||
* The dragging starts after "shorter" long click (the long click duration might change):
|
||||
* - When the touch gesture moves out of the {@code ACTION_DOWN} location the dragging
|
||||
* interaction is cancelled.
|
||||
* - When {@code ACTION_UP} happens before long click is registered and there was no significant
|
||||
* movement the view will perform click.
|
||||
* - When the listener registers long click it starts dragging interaction, all the subsequent
|
||||
* {@code ACTION_MOVE} events will drag the view, and the interaction finishes when
|
||||
* {@code ACTION_UP} or {@code ACTION_CANCEL} are received.
|
||||
* Lifecycle methods can be overridden do add extra setup/clean up steps.
|
||||
*/
|
||||
private abstract class BubbleTouchListener implements View.OnTouchListener {
|
||||
/**
|
||||
* The internal state of the touch listener
|
||||
*/
|
||||
private enum State {
|
||||
// Idle and ready for the touch events.
|
||||
// Changes to:
|
||||
// - TOUCHED, when the {@code ACTION_DOWN} is handled
|
||||
IDLE,
|
||||
|
||||
// Touch down was handled and the lister is recognising the gestures.
|
||||
// Changes to:
|
||||
// - IDLE, when performs the click
|
||||
// - DRAGGING, when registers the long click and starts dragging interaction
|
||||
// - CANCELLED, when the touch events move out of the initial location before the long
|
||||
// click is recognised
|
||||
|
||||
TOUCHED,
|
||||
|
||||
// The long click was registered and the view is being dragged.
|
||||
// Changes to:
|
||||
// - IDLE, when the gesture ends with the {@code ACTION_UP} or {@code ACTION_CANCEL}
|
||||
DRAGGING,
|
||||
|
||||
// The dragging was cancelled.
|
||||
// Changes to:
|
||||
// - IDLE, when the current gesture completes
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
private final PointF mTouchDownLocation = new PointF();
|
||||
private final PointF mViewInitialPosition = new PointF();
|
||||
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
|
||||
private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
|
||||
private State mState = State.IDLE;
|
||||
private int mTouchSlop = -1;
|
||||
private BubbleDragAnimator mAnimator;
|
||||
@Nullable
|
||||
private Runnable mLongClickRunnable;
|
||||
|
||||
/**
|
||||
* Called when the dragging interaction has started
|
||||
*/
|
||||
abstract void onDragStart();
|
||||
|
||||
/**
|
||||
* Called when the dragging interaction has ended and all the animations have completed
|
||||
*/
|
||||
abstract void onDragEnd();
|
||||
|
||||
/**
|
||||
* Called when the dragged bubble is released outside of the dismiss target area and will
|
||||
* move back to its initial position
|
||||
*/
|
||||
protected void onDragRelease() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dragged bubble is released inside of the dismiss target area and will get
|
||||
* dismissed with animation
|
||||
*/
|
||||
protected void onDragDismiss() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
|
||||
updateVelocity(event);
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
return onTouchDown(view, event);
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
onTouchMove(view, event);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
onTouchUp(view, event);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
onTouchCancel(view, event);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The touch down starts the interaction and schedules the long click handler.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
* @return true if the gesture should be intercepted and handled, false otherwise. Note if
|
||||
* the false is returned subsequent events in the gesture won't get reported.
|
||||
*/
|
||||
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
mState = State.TOUCHED;
|
||||
mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
|
||||
mTouchDownLocation.set(event.getRawX(), event.getRawY());
|
||||
mViewInitialPosition.set(view.getTranslationX(), view.getTranslationY());
|
||||
setupLongClickHandler(view);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The move event drags the view or cancels the interaction if hasn't long clicked yet.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
|
||||
final float dx = event.getRawX() - mTouchDownLocation.x;
|
||||
final float dy = event.getRawY() - mTouchDownLocation.y;
|
||||
switch (mState) {
|
||||
case TOUCHED:
|
||||
final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
|
||||
if (movedOut) {
|
||||
// Moved out of the initial location before the long click was registered
|
||||
mState = State.CANCELLED;
|
||||
cleanUpLongClickHandler(view);
|
||||
}
|
||||
break;
|
||||
case DRAGGING:
|
||||
drag(view, event, dx, dy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On touch up performs click or finishes the dragging depending on the state.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchUp(@NonNull View view, @NonNull MotionEvent event) {
|
||||
switch (mState) {
|
||||
case TOUCHED:
|
||||
view.performClick();
|
||||
cleanUp(view);
|
||||
break;
|
||||
case DRAGGING:
|
||||
stopDragging(view, event);
|
||||
break;
|
||||
default:
|
||||
cleanUp(view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The gesture is cancelled and the interaction should clean up and complete.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchCancel(@NonNull View view, @NonNull MotionEvent event) {
|
||||
if (mState == State.DRAGGING) {
|
||||
stopDragging(view, event);
|
||||
} else {
|
||||
cleanUp(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void startDragging(@NonNull View view) {
|
||||
onDragStart();
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
mAnimator = new BubbleDragAnimator(view);
|
||||
mAnimator.animateFocused();
|
||||
mBubbleDismissController.setupDismissView(view, mAnimator);
|
||||
mBubbleDismissController.showDismissView();
|
||||
}
|
||||
|
||||
private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
|
||||
if (mBubbleDismissController.handleTouchEvent(event)) return;
|
||||
view.setTranslationX(mViewInitialPosition.x + dx);
|
||||
view.setTranslationY(mViewInitialPosition.y + dy);
|
||||
}
|
||||
|
||||
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
|
||||
Runnable onComplete = () -> {
|
||||
mActivity.setTaskbarWindowFullscreen(false);
|
||||
cleanUp(view);
|
||||
onDragEnd();
|
||||
};
|
||||
|
||||
if (mBubbleDismissController.handleTouchEvent(event)) {
|
||||
onDragDismiss();
|
||||
mAnimator.animateDismiss(mViewInitialPosition, onComplete);
|
||||
} else {
|
||||
onDragRelease();
|
||||
mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
|
||||
onComplete);
|
||||
}
|
||||
mBubbleDismissController.hideDismissView();
|
||||
}
|
||||
|
||||
private void setupLongClickHandler(@NonNull View view) {
|
||||
cleanUpLongClickHandler(view);
|
||||
mLongClickRunnable = () -> {
|
||||
// Register long click and start dragging interaction
|
||||
mState = State.DRAGGING;
|
||||
startDragging(view);
|
||||
};
|
||||
view.getHandler().postDelayed(mLongClickRunnable, mPressToDragTimeout);
|
||||
}
|
||||
|
||||
private void cleanUpLongClickHandler(@NonNull View view) {
|
||||
if (mLongClickRunnable == null || view.getHandler() == null) return;
|
||||
view.getHandler().removeCallbacks(mLongClickRunnable);
|
||||
mLongClickRunnable = null;
|
||||
}
|
||||
|
||||
private void cleanUp(@NonNull View view) {
|
||||
cleanUpLongClickHandler(view);
|
||||
mVelocityTracker.clear();
|
||||
mState = State.IDLE;
|
||||
}
|
||||
|
||||
private void updateVelocity(MotionEvent event) {
|
||||
final float deltaX = event.getRawX() - event.getX();
|
||||
final float deltaY = event.getRawY() - event.getY();
|
||||
event.offsetLocation(deltaX, deltaY);
|
||||
mVelocityTracker.addMovement(event);
|
||||
event.offsetLocation(-deltaX, -deltaY);
|
||||
}
|
||||
|
||||
private PointF getCurrentVelocity() {
|
||||
mVelocityTracker.computeCurrentVelocity(/* units = */ 1000);
|
||||
return new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user