/* * 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.launcher2; 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.os.Vibrator; import android.util.Log; 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.launcher.R; import java.util.ArrayList; /** * Class for initiating a drag within a view or across multiple views. */ public class DragController { private static final String TAG = "Launcher.DragController"; /** 表示这次拖动是一个复制行为,比如在桌面上移动图标 */ public static int DRAG_ACTION_MOVE = 0; /** 表示这次拖动是一个复制行为,比如从“所有程序”界面 放到桌面 */ public static int DRAG_ACTION_COPY = 1; private static final int SCROLL_DELAY = 500; private static final int RESCROLL_DELAY = 750; private static final int VIBRATE_DURATION = 15; 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; static final int SCROLL_NONE = -1; static final int SCROLL_LEFT = 0; static final int SCROLL_RIGHT = 1; private static final float MAX_FLING_DEGREES = 35f; private Launcher mLauncher; private Handler mHandler; private final Vibrator mVibrator; // temporaries to avoid gc thrash // 防GC的临时变量 private Rect mRectTemp = new Rect(); private final int[] mCoordinatesTemp = new int[2]; /** 是否正在拖动 */ private boolean mDragging; /** 按下事件的X坐标 */ private int mMotionDownX; /** 按下事件的X坐标 */ private int mMotionDownY; /** * 滑动到桌面左右边上的某区域时桌面就会滚动。就是这个区域 */ private int mScrollZone; private DropTarget.DragObject mDragObject; /** 可以接收本次drag的view和listener */ 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; /** * 拖动到左右边上的时候能够左右滚动的view。比如说桌面 */ private View mScrollView; private View mMoveTarget; private DragScroller mDragScroller; private int mScrollState = SCROLL_OUTSIDE_ZONE; private ScrollRunnable mScrollRunnable = new ScrollRunnable(); private DropTarget mLastDropTarget; private InputMethodManager mInputMethodManager; private int mLastTouch[] = new int[2]; private long mLastTouchUpTime = -1; private int mDistanceSinceScroll = 0;// 上次滚动停止后,手指移动的距离 private int mTmpPoint[] = new int[2]; private Rect mDragLayerRect = new Rect(); protected int mFlingToDeleteThresholdVelocity; private VelocityTracker mVelocityTracker; /** * 接收drag开始和结束事件的侦听器 */ 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, Object 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(); mVibrator = (Vibrator) launcher .getSystemService(Context.VIBRATOR_SERVICE); float density = r.getDisplayMetrics().density; mFlingToDeleteThresholdVelocity = (int) (r .getInteger(R.integer.config_flingToDeleteMinVelocity) * density); } public boolean dragging() { return mDragging; } /** * 开始拖动 * * @param v * 被拖动的view * @param bmp * 表示被拖动view和bitmap 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 * 拖动的动作:移动或者复制 either {@link #DRAG_ACTION_MOVE} or * {@link #DRAG_ACTION_COPY} * @param dragRegion * 拖动对象bitmap的区域 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, Object dragInfo, int dragAction, Rect dragRegion, float initialDragViewScale) { int[] loc = mCoordinatesTemp; mLauncher.getDragLayer().getLocationInDragLayer(v, loc); int dragLayerX = loc[0] + v.getPaddingLeft() + (int) ((initialDragViewScale * bmp.getWidth() - bmp .getWidth()) / 2); int dragLayerY = loc[1] + v.getPaddingTop() + (int) ((initialDragViewScale * bmp.getHeight() - bmp .getHeight()) / 2); startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, initialDragViewScale); // 如果是移动,则将原先的源view设置为不可见 if (dragAction == DRAG_ACTION_MOVE) { v.setVisibility(View.GONE); } } /** * Starts a drag. * * @param b * 拖动对象的图像,有可能会被缩放 * @param dragLayerX * 播放对象的图像的左侧 * @param dragLayerY * 播放对象的图像的上边 * @param source * 发起拖动的对象 * @param dragInfo * 代表被拖动对象的数据 * @param dragAction * 拖放的动作:移动或者复制 * @param dragRegion * 拖动对象bitmap的区域 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(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, float initialDragViewScale) { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); } // 隐藏软键盘,如有需要 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; mDragging = true; mDragObject = new DropTarget.DragObject(); mDragObject.dragComplete = false; mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); mDragObject.dragSource = source; mDragObject.dragInfo = dragInfo; mVibrator.vibrate(VIBRATE_DURATION); final DragView dragView = mDragObject.dragView = new DragView( mLauncher, b, registrationX, registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); if (dragOffset != null) { dragView.setDragVisualizeOffset(new Point(dragOffset)); } if (dragRegion != null) { dragView.setDragRegion(new Rect(dragRegion)); } dragView.show(mMotionDownX, mMotionDownY); handleMoveEvent(mMotionDownX, mMotionDownY); } /** * 将view画成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 mDragging; } public boolean isDragging() { return mDragging; } /** * 不放下而取消拖动 */ public void cancelDrag() { if (mDragging) { 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(ArrayList<String> packageNames, Context context) { // 如果正在拖动的app被删除了,取消拖动 if (mDragObject != null) { Object rawDragInfo = mDragObject.dragInfo; if (rawDragInfo instanceof ShortcutInfo) { ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; for (String pn : packageNames) { // Added null checks to prevent NPE we've seen in the wild if (dragInfo != null && dragInfo.intent != null) { boolean isSamePackage = dragInfo.getPackageName() .equals(pn); if (isSamePackage) { cancelDrag(); return; } } } } } } private void endDrag() { if (mDragging) { mDragging = 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 : mListeners) { listener.onDragEnd(); } } } releaseVelocityTracker(); } /** * 只有在drag view被清除的动作在endDrag()方法中被推迟的时候才执行。 换句话说,拖动执行完毕,而动画没有执行完毕,这个时候drag * view不会被清除,listener不会执行onDragEnd,而是等到动画执行完毕 */ void onDeferredEndDrag(DragView dragView) { dragView.remove(); // If we skipped calling onDragEnd() before, do it now for (DragListener listener : mListeners) { listener.onDragEnd(); } } void onDeferredEndFling(DropTarget.DragObject d) { d.dragSource.onFlingToDeleteCompleted(); } /** * Clamps the position to the drag layer bounds. * 返回x或y在draglayer中间,那么返回x或y如果在外面 ,返回邻近的边 */ 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; } long getLastGestureUpTime() { if (mDragging) { return System.currentTimeMillis(); } else { return mLastTouchUpTime; } } void resetLastGestureUpTime() { mLastTouchUpTime = -1; } /** * Call this from a drag source view. 由drag * source调用。dragsource有可能是workspace,folder或者appPagedView */ public boolean onInterceptTouchEvent(MotionEvent ev) { @SuppressWarnings("all") // suppress dead code warning final boolean debug = false; if (debug) { Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" + mDragging); } // 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_MOVE: break; case MotionEvent.ACTION_DOWN: // Remember location of down touch mMotionDownX = dragLayerX; mMotionDownY = dragLayerY; mLastDropTarget = null; break; case MotionEvent.ACTION_UP: mLastTouchUpTime = System.currentTimeMillis(); if (mDragging) { PointF vec = isFlingingToDelete(mDragObject.dragSource); if (vec != null) { dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); } else { drop(dragLayerX, dragLayerY); } } endDrag(); break; case MotionEvent.ACTION_CANCEL: cancelDrag(); break; } return mDragging; } /** * 设置处理移动事件的view */ 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); // 放置在某处? final int[] coordinates = mCoordinatesTemp; DropTarget dropTarget = findDropTarget(x, y, coordinates); mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; if (dropTarget != null) { DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); if (delegate != null) { dropTarget = delegate; } // 如果还没有设置dropTarget或者dropTarget已经发生了改变,则设置dropTarget,调用onDragEnter if (mLastDropTarget != dropTarget) { if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); } dropTarget.onDragEnter(mDragObject); } dropTarget.onDragOver(mDragObject); } else { // 如果没有dropTarget并且上次处理的mLastDropTarget不为空则mLastDropTarget调用移出事件 if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); } } mLastDropTarget = dropTarget; // 在滚动了一次之后,触摸点仍然在滚动区域里,这时不应该再次滚动。而是需要移动一点点小动作来触发再次滚动。或者等待一小会。 // 举例,在workspace移动图标到左侧的时候,手指一直保持在左侧,桌面不会一直右滚的 final int slop = ViewConfiguration.get(mLauncher) .getScaledWindowTouchSlop(); mDistanceSinceScroll += Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); mLastTouch[0] = x; mLastTouch[1] = y; final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; // 进入可滚动区域后仍然要等待一会儿以后再滚动。 // 在滚动之前进入了不滚动区域,则取消滚动 if (x < mScrollZone) { if (mScrollState == SCROLL_OUTSIDE_ZONE) { mScrollState = SCROLL_WAITING_IN_ZONE; if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) { mLauncher.getDragLayer().onEnterScrollArea(SCROLL_LEFT); mScrollRunnable.setDirection(SCROLL_LEFT); 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, SCROLL_RIGHT)) { mLauncher.getDragLayer().onEnterScrollArea(SCROLL_RIGHT); mScrollRunnable.setDirection(SCROLL_RIGHT); mHandler.postDelayed(mScrollRunnable, delay); } } } else { clearScrollRunnable(); } } public void forceMoveEvent() { if (mDragging) { handleMoveEvent(mDragObject.x, mDragObject.y); } } /** * 由drag source调用 */ public boolean onTouchEvent(MotionEvent ev) { if (!mDragging) { 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_MOVE: handleMoveEvent(dragLayerX, dragLayerY); break; case MotionEvent.ACTION_UP: // 确保我们在抬起点处理了事件 handleMoveEvent(dragLayerX, dragLayerY); mHandler.removeCallbacks(mScrollRunnable); if (mDragging) { PointF vec = isFlingingToDelete(mDragObject.dragSource); if (vec != null) { dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); } else { drop(dragLayerX, dragLayerY); } } endDrag(); break; case MotionEvent.ACTION_CANCEL: mHandler.removeCallbacks(mScrollRunnable); cancelDrag(); break; } return true; } /** * 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; } private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { final int[] coordinates = mCoordinatesTemp; mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; // Clean up dragging on the target if it's not the current fling delete // target otherwise, // start dragging to it. if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { mLastDropTarget.onDragExit(mDragObject); } // Drop onto the fling-to-delete target boolean accepted = false; mFlingToDeleteDropTarget.onDragEnter(mDragObject); // We must set dragComplete to true _only_ after we "enter" the // fling-to-delete target for // "drop" mDragObject.dragComplete = true; mFlingToDeleteDropTarget.onDragExit(mDragObject); if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y, vel); accepted = true; } mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, accepted); } private void drop(float x, float y) { final int[] coordinates = mCoordinatesTemp; final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; boolean accepted = false; if (dropTarget != null) { mDragObject.dragComplete = true; dropTarget.onDragExit(mDragObject); if (dropTarget.acceptDrop(mDragObject)) { dropTarget.onDrop(mDragObject); accepted = true; } } mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, 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.getHitRect(r); // Convert the hit rect to DragLayer coordinates target.getLocationInDragLayer(dropCoordinates); r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); mDragObject.x = x; mDragObject.y = y; if (r.contains(x, y)) { DropTarget delegate = target.getDropTargetDelegate(mDragObject); if (delegate != null) { target = delegate; target.getLocationInDragLayer(dropCoordinates); } // Make dropCoordinates relative to the DropTarget dropCoordinates[0] = x - dropCoordinates[0]; dropCoordinates[1] = y - dropCoordinates[1]; return target; } } return null; } public void setDragScoller(DragScroller scroller) { mDragScroller = scroller; } public void setWindowToken(IBinder token) { mWindowToken = token; } /** * 添加拖动侦听器 */ public void addDragListener(DragListener l) { mListeners.add(l); } /** * 删除拖动侦听器 */ public void removeDragListener(DragListener l) { mListeners.remove(l); } /** * 添加有可能接收放下事件的target */ public void addDropTarget(DropTarget target) { mDropTargets.add(target); } /** * target将不再接收放下事件 */ 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; } } /** * 拖动拖动到连上的时候要滚动的view,一般是workspace什么的 */ public void setScrollView(View v) { mScrollView = v; } DragView getDragView() { return mDragObject.dragView; } /** * 用来滚动scrollview通常也就是桌面的 */ 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()) { // 如果手指停止了还未抬起,那么仍然要处理 forceMoveEvent(); } } } void setDirection(int direction) { mDirection = direction; } } }