Moving drag-drop related code into a separate package.
This is per an earlier CR comment "we should probably move all this code to its own package (launcher3.dragndrop) in a separate cl". I'm not moving DragSource because it's referred from gsa code. Bug: 22609426 Change-Id: Ia7204dab99c0c395c66b77143a2d60411153f5f3
This commit is contained in:
@@ -0,0 +1,887 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.dragndrop;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import android.view.DragEvent;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.android.launcher3.DeleteDropTarget;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.PagedView;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Class for initiating a drag within a view or across multiple views.
|
||||
*/
|
||||
public class DragController implements DragDriver.EventListener {
|
||||
private static final String TAG = "Launcher.DragController";
|
||||
|
||||
/** Indicates the drag is a move. */
|
||||
public static int DRAG_ACTION_MOVE = 0;
|
||||
|
||||
/** Indicates the drag is a copy. */
|
||||
public static int DRAG_ACTION_COPY = 1;
|
||||
|
||||
public static final int SCROLL_DELAY = 500;
|
||||
public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
|
||||
|
||||
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
|
||||
|
||||
private static final int SCROLL_OUTSIDE_ZONE = 0;
|
||||
private static final int SCROLL_WAITING_IN_ZONE = 1;
|
||||
|
||||
public static final int SCROLL_NONE = -1;
|
||||
public static final int SCROLL_LEFT = 0;
|
||||
public static final int SCROLL_RIGHT = 1;
|
||||
|
||||
private static final float MAX_FLING_DEGREES = 35f;
|
||||
|
||||
@Thunk Launcher mLauncher;
|
||||
private Handler mHandler;
|
||||
|
||||
// temporaries to avoid gc thrash
|
||||
private Rect mRectTemp = new Rect();
|
||||
private final int[] mCoordinatesTemp = new int[2];
|
||||
private final boolean mIsRtl;
|
||||
|
||||
/**
|
||||
* Drag driver for the current drag/drop operation, or null if there is no active DND operation.
|
||||
* It's null during accessible drag operations.
|
||||
*/
|
||||
private DragDriver mDragDriver = null;
|
||||
|
||||
/** Whether or not an accessible drag operation is in progress. */
|
||||
private boolean mIsAccessibleDrag;
|
||||
|
||||
/** X coordinate of the down event. */
|
||||
private int mMotionDownX;
|
||||
|
||||
/** Y coordinate of the down event. */
|
||||
private int mMotionDownY;
|
||||
|
||||
/** the area at the edge of the screen that makes the workspace go left
|
||||
* or right while you're dragging.
|
||||
*/
|
||||
private final int mScrollZone;
|
||||
|
||||
private DropTarget.DragObject mDragObject;
|
||||
|
||||
/** Who can receive drop events */
|
||||
private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
|
||||
private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
|
||||
private DropTarget mFlingToDeleteDropTarget;
|
||||
|
||||
/** The window token used as the parent for the DragView. */
|
||||
private IBinder mWindowToken;
|
||||
|
||||
/** The view that will be scrolled when dragging to the left and right edges of the screen. */
|
||||
private View mScrollView;
|
||||
|
||||
private View mMoveTarget;
|
||||
|
||||
@Thunk DragScroller mDragScroller;
|
||||
@Thunk int mScrollState = SCROLL_OUTSIDE_ZONE;
|
||||
private ScrollRunnable mScrollRunnable = new ScrollRunnable();
|
||||
|
||||
private DropTarget mLastDropTarget;
|
||||
|
||||
private InputMethodManager mInputMethodManager;
|
||||
|
||||
@Thunk int mLastTouch[] = new int[2];
|
||||
@Thunk long mLastTouchUpTime = -1;
|
||||
@Thunk int mDistanceSinceScroll = 0;
|
||||
|
||||
private int mTmpPoint[] = new int[2];
|
||||
private Rect mDragLayerRect = new Rect();
|
||||
|
||||
protected final int mFlingToDeleteThresholdVelocity;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
|
||||
/**
|
||||
* Interface to receive notifications when a drag starts or stops
|
||||
*/
|
||||
public interface DragListener {
|
||||
/**
|
||||
* A drag has begun
|
||||
*
|
||||
* @param source An object representing where the drag originated
|
||||
* @param info The data associated with the object that is being dragged
|
||||
* @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
|
||||
* or {@link DragController#DRAG_ACTION_COPY}
|
||||
*/
|
||||
void onDragStart(DragSource source, ItemInfo info, int dragAction);
|
||||
|
||||
/**
|
||||
* The drag has ended
|
||||
*/
|
||||
void onDragEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create a new DragLayer from XML.
|
||||
*
|
||||
* @param context The application's context.
|
||||
*/
|
||||
public DragController(Launcher launcher) {
|
||||
Resources r = launcher.getResources();
|
||||
mLauncher = launcher;
|
||||
mHandler = new Handler();
|
||||
mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
|
||||
float density = r.getDisplayMetrics().density;
|
||||
mFlingToDeleteThresholdVelocity =
|
||||
(int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
|
||||
mIsRtl = Utilities.isRtl(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a drag.
|
||||
*
|
||||
* @param v The view that is being dragged
|
||||
* @param bmp The bitmap that represents the view being dragged
|
||||
* @param source An object representing where the drag originated
|
||||
* @param dragInfo The data associated with the object that is being dragged
|
||||
* @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
|
||||
* {@link #DRAG_ACTION_COPY}
|
||||
* @param viewImageBounds the position of the image inside the view
|
||||
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
|
||||
* Makes dragging feel more precise, e.g. you can clip out a transparent border
|
||||
*/
|
||||
public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo,
|
||||
Rect viewImageBounds, int dragAction, float initialDragViewScale) {
|
||||
int[] loc = mCoordinatesTemp;
|
||||
mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
|
||||
int dragLayerX = loc[0] + viewImageBounds.left
|
||||
+ (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
|
||||
int dragLayerY = loc[1] + viewImageBounds.top
|
||||
+ (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
|
||||
|
||||
startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
|
||||
null, initialDragViewScale, false);
|
||||
|
||||
if (dragAction == DRAG_ACTION_MOVE) {
|
||||
v.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a drag.
|
||||
*
|
||||
* @param b The bitmap to display as the drag image. It will be re-scaled to the
|
||||
* enlarged size.
|
||||
* @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
|
||||
* @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
|
||||
* @param source An object representing where the drag originated
|
||||
* @param dragInfo The data associated with the object that is being dragged
|
||||
* @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
|
||||
* {@link #DRAG_ACTION_COPY}
|
||||
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
|
||||
* Makes dragging feel more precise, e.g. you can clip out a transparent border
|
||||
* @param accessible whether this drag should occur in accessibility mode
|
||||
*/
|
||||
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
|
||||
DragSource source, ItemInfo dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
|
||||
float initialDragViewScale, boolean accessible) {
|
||||
if (PROFILE_DRAWING_DURING_DRAG) {
|
||||
android.os.Debug.startMethodTracing("Launcher");
|
||||
}
|
||||
|
||||
// Hide soft keyboard, if visible
|
||||
if (mInputMethodManager == null) {
|
||||
mInputMethodManager = (InputMethodManager)
|
||||
mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
|
||||
|
||||
for (DragListener listener : mListeners) {
|
||||
listener.onDragStart(source, dragInfo, dragAction);
|
||||
}
|
||||
|
||||
final int registrationX = mMotionDownX - dragLayerX;
|
||||
final int registrationY = mMotionDownY - dragLayerY;
|
||||
|
||||
final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
|
||||
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
|
||||
|
||||
mIsAccessibleDrag = accessible;
|
||||
mLastDropTarget = null;
|
||||
|
||||
mDragObject = new DropTarget.DragObject();
|
||||
|
||||
mDragObject.dragComplete = false;
|
||||
if (mIsAccessibleDrag) {
|
||||
// For an accessible drag, we assume the view is being dragged from the center.
|
||||
mDragObject.xOffset = b.getWidth() / 2;
|
||||
mDragObject.yOffset = b.getHeight() / 2;
|
||||
mDragObject.accessibleDrag = true;
|
||||
} else {
|
||||
mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
|
||||
mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
|
||||
}
|
||||
|
||||
mDragObject.dragSource = source;
|
||||
mDragObject.dragInfo = dragInfo;
|
||||
|
||||
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
|
||||
registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
|
||||
|
||||
if (!accessible) {
|
||||
mDragDriver = DragDriver.create(this, dragInfo, dragView);
|
||||
}
|
||||
|
||||
if (dragOffset != null) {
|
||||
dragView.setDragVisualizeOffset(new Point(dragOffset));
|
||||
}
|
||||
if (dragRegion != null) {
|
||||
dragView.setDragRegion(new Rect(dragRegion));
|
||||
}
|
||||
|
||||
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
dragView.show(mMotionDownX, mMotionDownY);
|
||||
handleMoveEvent(mMotionDownX, mMotionDownY);
|
||||
return dragView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the view into a bitmap.
|
||||
*/
|
||||
Bitmap getViewBitmap(View v) {
|
||||
v.clearFocus();
|
||||
v.setPressed(false);
|
||||
|
||||
boolean willNotCache = v.willNotCacheDrawing();
|
||||
v.setWillNotCacheDrawing(false);
|
||||
|
||||
// Reset the drawing cache background color to fully transparent
|
||||
// for the duration of this operation
|
||||
int color = v.getDrawingCacheBackgroundColor();
|
||||
v.setDrawingCacheBackgroundColor(0);
|
||||
float alpha = v.getAlpha();
|
||||
v.setAlpha(1.0f);
|
||||
|
||||
if (color != 0) {
|
||||
v.destroyDrawingCache();
|
||||
}
|
||||
v.buildDrawingCache();
|
||||
Bitmap cacheBitmap = v.getDrawingCache();
|
||||
if (cacheBitmap == null) {
|
||||
Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
|
||||
|
||||
// Restore the view
|
||||
v.destroyDrawingCache();
|
||||
v.setAlpha(alpha);
|
||||
v.setWillNotCacheDrawing(willNotCache);
|
||||
v.setDrawingCacheBackgroundColor(color);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from a drag source view like this:
|
||||
*
|
||||
* <pre>
|
||||
* @Override
|
||||
* public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
* return mDragController.dispatchKeyEvent(this, event)
|
||||
* || super.dispatchKeyEvent(event);
|
||||
* </pre>
|
||||
*/
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
return mDragDriver != null;
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return mDragDriver != null || mIsAccessibleDrag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop dragging without dropping.
|
||||
*/
|
||||
public void cancelDrag() {
|
||||
if (isDragging()) {
|
||||
if (mLastDropTarget != null) {
|
||||
mLastDropTarget.onDragExit(mDragObject);
|
||||
}
|
||||
mDragObject.deferDragViewCleanupPostAnimation = false;
|
||||
mDragObject.cancelled = true;
|
||||
mDragObject.dragComplete = true;
|
||||
mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
|
||||
}
|
||||
endDrag();
|
||||
}
|
||||
|
||||
public void onAppsRemoved(final ArrayList<String> packageNames, HashSet<ComponentName> cns) {
|
||||
// Cancel the current drag if we are removing an app that we are dragging
|
||||
if (mDragObject != null) {
|
||||
Object rawDragInfo = mDragObject.dragInfo;
|
||||
if (rawDragInfo instanceof ShortcutInfo) {
|
||||
ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
|
||||
for (ComponentName componentName : cns) {
|
||||
// Added null checks to prevent NPE we've seen in the wild
|
||||
if (dragInfo != null && dragInfo.intent != null) {
|
||||
ComponentName cn = dragInfo.intent.getComponent();
|
||||
boolean isSameComponent = cn != null && (cn.equals(componentName) ||
|
||||
packageNames.contains(cn.getPackageName()));
|
||||
if (isSameComponent) {
|
||||
cancelDrag();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endDrag() {
|
||||
if (isDragging()) {
|
||||
mDragDriver = null;
|
||||
mIsAccessibleDrag = false;
|
||||
clearScrollRunnable();
|
||||
boolean isDeferred = false;
|
||||
if (mDragObject.dragView != null) {
|
||||
isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
|
||||
if (!isDeferred) {
|
||||
mDragObject.dragView.remove();
|
||||
}
|
||||
mDragObject.dragView = null;
|
||||
}
|
||||
|
||||
// Only end the drag if we are not deferred
|
||||
if (!isDeferred) {
|
||||
for (DragListener listener : new ArrayList<>(mListeners)) {
|
||||
listener.onDragEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
releaseVelocityTracker();
|
||||
}
|
||||
|
||||
/**
|
||||
* This only gets called as a result of drag view cleanup being deferred in endDrag();
|
||||
*/
|
||||
void onDeferredEndDrag(DragView dragView) {
|
||||
dragView.remove();
|
||||
|
||||
if (mDragObject.deferDragViewCleanupPostAnimation) {
|
||||
// If we skipped calling onDragEnd() before, do it now
|
||||
for (DragListener listener : new ArrayList<>(mListeners)) {
|
||||
listener.onDragEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeferredEndFling(DropTarget.DragObject d) {
|
||||
d.dragSource.onFlingToDeleteCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps the position to the drag layer bounds.
|
||||
*/
|
||||
private int[] getClampedDragLayerPos(float x, float y) {
|
||||
mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
|
||||
mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
|
||||
mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
|
||||
return mTmpPoint;
|
||||
}
|
||||
|
||||
public long getLastGestureUpTime() {
|
||||
if (mDragDriver != null) {
|
||||
return System.currentTimeMillis();
|
||||
} else {
|
||||
return mLastTouchUpTime;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetLastGestureUpTime() {
|
||||
mLastTouchUpTime = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDriverDragMove(float x, float y) {
|
||||
final int[] dragLayerPos = getClampedDragLayerPos(x, y);
|
||||
|
||||
handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDriverDragExitWindow() {
|
||||
if (mLastDropTarget != null) {
|
||||
mLastDropTarget.onDragExit(mDragObject);
|
||||
mLastDropTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) {
|
||||
final int[] dragLayerPos = getClampedDragLayerPos(x, y);
|
||||
final int dragLayerX = dragLayerPos[0];
|
||||
final int dragLayerY = dragLayerPos[1];
|
||||
|
||||
DropTarget dropTarget;
|
||||
PointF vec = null;
|
||||
|
||||
if (dropTargetOverride != null) {
|
||||
dropTarget = dropTargetOverride;
|
||||
} else {
|
||||
vec = isFlingingToDelete(mDragObject.dragSource);
|
||||
if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
|
||||
vec = null;
|
||||
}
|
||||
if (vec != null) {
|
||||
dropTarget = mFlingToDeleteDropTarget;
|
||||
} else {
|
||||
dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
|
||||
}
|
||||
}
|
||||
|
||||
drop(dropTarget, x, y, vec);
|
||||
|
||||
endDrag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDriverDragCancel() {
|
||||
cancelDrag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from a drag source view.
|
||||
*/
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
@SuppressWarnings("all") // suppress dead code warning
|
||||
final boolean debug = false;
|
||||
if (debug) {
|
||||
Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging="
|
||||
+ (mDragDriver != null));
|
||||
}
|
||||
|
||||
if (mIsAccessibleDrag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the velocity tracker
|
||||
acquireVelocityTrackerAndAddMovement(ev);
|
||||
|
||||
final int action = ev.getAction();
|
||||
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
|
||||
final int dragLayerX = dragLayerPos[0];
|
||||
final int dragLayerY = dragLayerPos[1];
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Remember location of down touch
|
||||
mMotionDownX = dragLayerX;
|
||||
mMotionDownY = dragLayerY;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mLastTouchUpTime = System.currentTimeMillis();
|
||||
break;
|
||||
}
|
||||
|
||||
return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from a drag source view.
|
||||
*/
|
||||
public boolean onDragEvent(DragEvent event) {
|
||||
return mDragDriver != null && mDragDriver.onDragEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from a drag view.
|
||||
*/
|
||||
public void onDragViewAnimationEnd() {
|
||||
if (mDragDriver != null) {
|
||||
mDragDriver.onDragViewAnimationEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the view that should handle move events.
|
||||
*/
|
||||
public void setMoveTarget(View view) {
|
||||
mMoveTarget = view;
|
||||
}
|
||||
|
||||
public boolean dispatchUnhandledMove(View focused, int direction) {
|
||||
return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
|
||||
}
|
||||
|
||||
private void clearScrollRunnable() {
|
||||
mHandler.removeCallbacks(mScrollRunnable);
|
||||
if (mScrollState == SCROLL_WAITING_IN_ZONE) {
|
||||
mScrollState = SCROLL_OUTSIDE_ZONE;
|
||||
mScrollRunnable.setDirection(SCROLL_RIGHT);
|
||||
mDragScroller.onExitScrollArea();
|
||||
mLauncher.getDragLayer().onExitScrollArea();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMoveEvent(int x, int y) {
|
||||
mDragObject.dragView.move(x, y);
|
||||
|
||||
// Drop on someone?
|
||||
final int[] coordinates = mCoordinatesTemp;
|
||||
DropTarget dropTarget = findDropTarget(x, y, coordinates);
|
||||
mDragObject.x = coordinates[0];
|
||||
mDragObject.y = coordinates[1];
|
||||
checkTouchMove(dropTarget);
|
||||
|
||||
// Check if we are hovering over the scroll areas
|
||||
mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
|
||||
mLastTouch[0] = x;
|
||||
mLastTouch[1] = y;
|
||||
checkScrollState(x, y);
|
||||
}
|
||||
|
||||
public void forceTouchMove() {
|
||||
int[] dummyCoordinates = mCoordinatesTemp;
|
||||
DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
|
||||
mDragObject.x = dummyCoordinates[0];
|
||||
mDragObject.y = dummyCoordinates[1];
|
||||
checkTouchMove(dropTarget);
|
||||
}
|
||||
|
||||
private void checkTouchMove(DropTarget dropTarget) {
|
||||
if (dropTarget != null) {
|
||||
if (mLastDropTarget != dropTarget) {
|
||||
if (mLastDropTarget != null) {
|
||||
mLastDropTarget.onDragExit(mDragObject);
|
||||
}
|
||||
dropTarget.onDragEnter(mDragObject);
|
||||
}
|
||||
dropTarget.onDragOver(mDragObject);
|
||||
} else {
|
||||
if (mLastDropTarget != null) {
|
||||
mLastDropTarget.onDragExit(mDragObject);
|
||||
}
|
||||
}
|
||||
mLastDropTarget = dropTarget;
|
||||
}
|
||||
|
||||
@Thunk void checkScrollState(int x, int y) {
|
||||
final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
|
||||
final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
|
||||
final DragLayer dragLayer = mLauncher.getDragLayer();
|
||||
final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT;
|
||||
final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT;
|
||||
|
||||
if (x < mScrollZone) {
|
||||
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
|
||||
mScrollState = SCROLL_WAITING_IN_ZONE;
|
||||
if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
|
||||
dragLayer.onEnterScrollArea(forwardDirection);
|
||||
mScrollRunnable.setDirection(forwardDirection);
|
||||
mHandler.postDelayed(mScrollRunnable, delay);
|
||||
}
|
||||
}
|
||||
} else if (x > mScrollView.getWidth() - mScrollZone) {
|
||||
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
|
||||
mScrollState = SCROLL_WAITING_IN_ZONE;
|
||||
if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
|
||||
dragLayer.onEnterScrollArea(backwardsDirection);
|
||||
mScrollRunnable.setDirection(backwardsDirection);
|
||||
mHandler.postDelayed(mScrollRunnable, delay);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearScrollRunnable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from a drag source view.
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (mDragDriver == null || mIsAccessibleDrag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the velocity tracker
|
||||
acquireVelocityTrackerAndAddMovement(ev);
|
||||
|
||||
final int action = ev.getAction();
|
||||
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
|
||||
final int dragLayerX = dragLayerPos[0];
|
||||
final int dragLayerY = dragLayerPos[1];
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Remember where the motion event started
|
||||
mMotionDownX = dragLayerX;
|
||||
mMotionDownY = dragLayerY;
|
||||
|
||||
if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
|
||||
mScrollState = SCROLL_WAITING_IN_ZONE;
|
||||
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
|
||||
} else {
|
||||
mScrollState = SCROLL_OUTSIDE_ZONE;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mHandler.removeCallbacks(mScrollRunnable);
|
||||
break;
|
||||
}
|
||||
|
||||
return mDragDriver.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since accessible drag and drop won't cause the same sequence of touch events, we manually
|
||||
* inject the appropriate state.
|
||||
*/
|
||||
public void prepareAccessibleDrag(int x, int y) {
|
||||
mMotionDownX = x;
|
||||
mMotionDownY = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* As above, since accessible drag and drop won't cause the same sequence of touch events,
|
||||
* we manually ensure appropriate drag and drop events get emulated for accessible drag.
|
||||
*/
|
||||
public void completeAccessibleDrag(int[] location) {
|
||||
final int[] coordinates = mCoordinatesTemp;
|
||||
|
||||
// We make sure that we prime the target for drop.
|
||||
DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
|
||||
mDragObject.x = coordinates[0];
|
||||
mDragObject.y = coordinates[1];
|
||||
checkTouchMove(dropTarget);
|
||||
|
||||
dropTarget.prepareAccessibilityDrop();
|
||||
// Perform the drop
|
||||
drop(dropTarget, location[0], location[1], null);
|
||||
endDrag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user flung the current item to delete it.
|
||||
*
|
||||
* @return the vector at which the item was flung, or null if no fling was detected.
|
||||
*/
|
||||
private PointF isFlingingToDelete(DragSource source) {
|
||||
if (mFlingToDeleteDropTarget == null) return null;
|
||||
if (!source.supportsFlingToDelete()) return null;
|
||||
|
||||
ViewConfiguration config = ViewConfiguration.get(mLauncher);
|
||||
mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
|
||||
|
||||
if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
|
||||
// Do a quick dot product test to ensure that we are flinging upwards
|
||||
PointF vel = new PointF(mVelocityTracker.getXVelocity(),
|
||||
mVelocityTracker.getYVelocity());
|
||||
PointF upVec = new PointF(0f, -1f);
|
||||
float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
|
||||
(vel.length() * upVec.length()));
|
||||
if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
|
||||
return vel;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
|
||||
final int[] coordinates = mCoordinatesTemp;
|
||||
|
||||
mDragObject.x = coordinates[0];
|
||||
mDragObject.y = coordinates[1];
|
||||
|
||||
// Move dragging to the final target.
|
||||
if (dropTarget != mLastDropTarget) {
|
||||
if (mLastDropTarget != null) {
|
||||
mLastDropTarget.onDragExit(mDragObject);
|
||||
}
|
||||
mLastDropTarget = dropTarget;
|
||||
if (dropTarget != null) {
|
||||
dropTarget.onDragEnter(mDragObject);
|
||||
}
|
||||
}
|
||||
|
||||
mDragObject.dragComplete = true;
|
||||
|
||||
// Drop onto the target.
|
||||
boolean accepted = false;
|
||||
if (dropTarget != null) {
|
||||
dropTarget.onDragExit(mDragObject);
|
||||
if (dropTarget.acceptDrop(mDragObject)) {
|
||||
if (flingVel != null) {
|
||||
dropTarget.onFlingToDelete(mDragObject, flingVel);
|
||||
} else {
|
||||
dropTarget.onDrop(mDragObject);
|
||||
}
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
|
||||
mDragObject.dragSource.onDropCompleted(
|
||||
dropTargetAsView, mDragObject, flingVel != null, accepted);
|
||||
}
|
||||
|
||||
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
|
||||
final Rect r = mRectTemp;
|
||||
|
||||
final ArrayList<DropTarget> dropTargets = mDropTargets;
|
||||
final int count = dropTargets.size();
|
||||
for (int i=count-1; i>=0; i--) {
|
||||
DropTarget target = dropTargets.get(i);
|
||||
if (!target.isDropEnabled())
|
||||
continue;
|
||||
|
||||
target.getHitRectRelativeToDragLayer(r);
|
||||
|
||||
mDragObject.x = x;
|
||||
mDragObject.y = y;
|
||||
if (r.contains(x, y)) {
|
||||
|
||||
dropCoordinates[0] = x;
|
||||
dropCoordinates[1] = y;
|
||||
mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setDragScoller(DragScroller scroller) {
|
||||
mDragScroller = scroller;
|
||||
}
|
||||
|
||||
public void setWindowToken(IBinder token) {
|
||||
mWindowToken = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the drag listner which will be notified when a drag starts or ends.
|
||||
*/
|
||||
public void addDragListener(DragListener l) {
|
||||
mListeners.add(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a previously installed drag listener.
|
||||
*/
|
||||
public void removeDragListener(DragListener l) {
|
||||
mListeners.remove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a DropTarget to the list of potential places to receive drop events.
|
||||
*/
|
||||
public void addDropTarget(DropTarget target) {
|
||||
mDropTargets.add(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't send drop events to <em>target</em> any more.
|
||||
*/
|
||||
public void removeDropTarget(DropTarget target) {
|
||||
mDropTargets.remove(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current fling-to-delete drop target.
|
||||
*/
|
||||
public void setFlingToDeleteDropTarget(DropTarget target) {
|
||||
mFlingToDeleteDropTarget = target;
|
||||
}
|
||||
|
||||
private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mVelocityTracker.addMovement(ev);
|
||||
}
|
||||
|
||||
private void releaseVelocityTracker() {
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which view scrolls for touch events near the edge of the screen.
|
||||
*/
|
||||
public void setScrollView(View v) {
|
||||
mScrollView = v;
|
||||
}
|
||||
|
||||
private class ScrollRunnable implements Runnable {
|
||||
private int mDirection;
|
||||
|
||||
ScrollRunnable() {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (mDragScroller != null) {
|
||||
if (mDirection == SCROLL_LEFT) {
|
||||
mDragScroller.scrollLeft();
|
||||
} else {
|
||||
mDragScroller.scrollRight();
|
||||
}
|
||||
mScrollState = SCROLL_OUTSIDE_ZONE;
|
||||
mDistanceSinceScroll = 0;
|
||||
mDragScroller.onExitScrollArea();
|
||||
mLauncher.getDragLayer().onExitScrollArea();
|
||||
|
||||
if (isDragging()) {
|
||||
// Check the scroll again so that we can requeue the scroller if necessary
|
||||
checkScrollState(mLastTouch[0], mLastTouch[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setDirection(int direction) {
|
||||
mDirection = direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user