public boolean onLongClick(View v) { if (!isDraggingEnabled()) return false; if (isWorkspaceLocked()) return false; if (mState != State.WORKSPACE) return false; // 长按空白处 if (v instanceof Workspace) { if (!mWorkspace.isInOverviewMode()) {// 判断是否在缩略图模式下 if (mWorkspace.enterOverviewMode()) {// 进入缩略图模式 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); return true; } else { return false; } } else { return false; } } // 非空白处 CellLayout.CellInfo longClickCellInfo = null; View itemUnderLongClick = null; if (v.getTag() instanceof ItemInfo) {// ItemInfo子类 ItemInfo info = (ItemInfo) v.getTag(); longClickCellInfo = new CellLayout.CellInfo(v, info);; itemUnderLongClick = longClickCellInfo.cell; resetAddInfo(); } // The hotseat touch handling does not go through Workspace, and we always allow long press // on hotseat items. final boolean inHotseat = isHotseatLayout(v);// 是否热键栏 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();// 是否允许长按处理 if (allowLongPress && !mDragController.isDragging()) {// 允许长按 && 没有进行拖拽 if (itemUnderLongClick == null) {// 如果itemUnderLongClick为null,进行长按空白处一样的处理 // User long pressed on empty space mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); if (mWorkspace.isInOverviewMode()) { mWorkspace.startReordering(v); } else { mWorkspace.enterOverviewMode(); } } else { final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank( mHotseat.getOrderInHotseat( longClickCellInfo.cellX, longClickCellInfo.cellY));// 判断长按是否allapp按钮 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {// 长按的不是allapp按钮也不在文件夹展开的布局中 // User long pressed on an item mWorkspace.startDrag(longClickCellInfo);// 开始拖拽 } } } return true; }该方法用来执行长按事件,整个长按操作分几种情况,不同情况的处理也是不一样的,流程图表示如下,
mWorkspace.startDrag(longClickCellInfo);// 开始拖拽
void startDrag(CellLayout.CellInfo cellInfo) { View child = cellInfo.cell; // Make sure the drag was started by a long press as opposed to a long click. if (!child.isInTouchMode()) { return; } mDragInfo = cellInfo;// 更新单元信息 child.setVisibility(INVISIBLE);// 拖拽对象在原来的位置设为不可见 CellLayout layout = (CellLayout) child.getParent().getParent();// 拖拽对象所在的屏幕 layout.prepareChildForDrag(child); beginDragShared(child, this); }
public void beginDragShared(View child, DragSource source) { child.clearFocus(); child.setPressed(false); // The outline is used to visualize where the item will land if dropped // 创建拖拽对象投射轮廓 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING); mLauncher.onDragStarted(child); // The drag bitmap follows the touch point around on the screen AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING); final Bitmap b = createDragBitmap(child, padding);// 创建拖拽图像 final int bmpWidth = b.getWidth(); final int bmpHeight = b.getHeight(); float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2); int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 - padding.get() / 2); LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); Point dragVisualizeOffset = null; Rect dragRect = null; if (child instanceof BubbleTextView) { int iconSize = grid.iconSizePx; int top = child.getPaddingTop(); int left = (bmpWidth - iconSize) / 2; int right = left + iconSize; int bottom = top + iconSize; dragLayerY += top; // Note: The drag region is used to calculate drag layer offsets, but the // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2); dragRect = new Rect(left, top, right, bottom); } else if (child instanceof FolderIcon) { int previewSize = grid.folderIconSizePx; dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize); } // Clear the pressed state if necessary if (child instanceof BubbleTextView) { BubbleTextView icon = (BubbleTextView) child; icon.clearPressedBackground(); } if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) { String msg = "Drag started with a view that has no tag set. This " + "will cause a crash (issue 11627249) down the line. " + "View: " + child + " tag: " + child.getTag(); throw new IllegalStateException(msg); } // 创建拖拽视图 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); if (child.getParent() instanceof ShortcutAndWidgetContainer) { mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); } b.recycle(); }
public DragView 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"); } // 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; // 记录当前的状态 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; // 创建DragView对象 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)); } // 触摸反馈 mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); // 显示DragView对象(将该DragView添加到DragLayer上) dragView.show(mMotionDownX, mMotionDownY); // 根据当前的位置处理移动事件 handleMoveEvent(mMotionDownX, mMotionDownY); return dragView; }
public void onDragStart(final DragSource source, Object info, int dragAction) { Log.d(TAG, "onDragStart..."); mIsDragOccuring = true; updateChildrenLayersEnabled(false); mLauncher.lockScreenOrientation();// 锁定屏幕 mLauncher.onInteractionBegin(); setChildrenBackgroundAlphaMultipliers(1f); // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging // 正在拖拽的时候,防止卸载或安装导致快捷图标变化更新数据库的操作 InstallShortcutReceiver.enableInstallQueue(); UninstallShortcutReceiver.enableUninstallQueue(); post(new Runnable() { @Override public void run() { if (mIsDragOccuring) { mDeferRemoveExtraEmptyScreen = false; addExtraEmptyScreenOnDrag();// 添加额外的空白页 } } }); }
// 显示DragView对象(将该DragView添加到DragLayer上) public void show(int touchX, int touchY) { mDragLayer.addView(this); // Start the pick-up animation DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0); lp.width = mBitmap.getWidth(); lp.height = mBitmap.getHeight(); lp.customPosition = true; setLayoutParams(lp); // 设置显示位置 setTranslationX(touchX - mRegistrationX); setTranslationY(touchY - mRegistrationY); // Post the animation to skip other expensive work happening on the first frame //动画播放 post(new Runnable() { public void run() { mAnim.start(); } }); }
// 根据当前的位置处理移动事件 private void handleMoveEvent(int x, int y) { // 移动View,DragView继承至View,在方法move()中设置setTranslationX()、setTranslationY()即可移动view 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.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); mLastTouch[0] = x; mLastTouch[1] = y; checkScrollState(x, y); // 对拖动时的翻页进行判断处理 }方法中都有注释,还是比较容易理解的,其中 handleMoveEvent处理拖拽时的移动事件,是整个过程的一个很重要的方法,在下面的讲解中会详细介绍。
DragLayer继承自ViewGroup,其onInterceptTouchEvent方法若返回true,说明需要拦截触屏事件,则后续的一系列事件将传递给自身的onTouchEvent方法,而不再向其子控件传递。DragController的onInterceptTouchEvent由DragLayer的onInterceptTouchEvent调用,用于拦截触屏事件的处理。当用户点击屏幕时,触发ACTION_DOWN事件,记录当前触摸位置。当抬起时,触发ACTION_UP事件,结束拖拽。若抬起时处于拖拽中,在当前位置释放被拖拽物。因此,若此时处于拖拽中,后续的触屏事件将只传递到DragLayer的onTouchEvent。
onTouchEvent
onTouchEvent处理触屏事件,若返回true,则表示消费掉该事件,事件不再向父控件的onTouchEvent传递。DragController的onTouchEvent由DragLayer的onTouchEvent调用,用于处理被拖拽物的移动。当startDrag执行完毕,DragController设置拖拽状态为true,这样,触屏事件将最终转到onTouchEvent中,在此处调用handleMoveEvent进行物体的移动。
/** * Call this from a drag source view. */ //DragLayer是Launcher所有布局的父容器,它的onInterceptTouchEvent()已交由DragController.onInterceptTouchEvent()来处理 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 (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); } else { drop(dragLayerX, dragLayerY); } } endDrag(); break; case MotionEvent.ACTION_CANCEL: cancelDrag(); break; } return mDragging; }是否拦截,主要看返回值,这里返回 mDragging,在 startDrag的时候设为true,所以DragLayer对touch事件进行了拦截,在 DragController的 onTouchEvent中进行处理,
/** * Call this from a drag source view. */ 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; } handleMoveEvent(dragLayerX, dragLayerY); break; case MotionEvent.ACTION_MOVE: handleMoveEvent(dragLayerX, dragLayerY); break; case MotionEvent.ACTION_UP: // Ensure that we've processed a move event at the current pointer location. handleMoveEvent(dragLayerX, dragLayerY); mHandler.removeCallbacks(mScrollRunnable); if (mDragging) { // 判断是否到达可删除的区域 PointF vec = isFlingingToDelete(mDragObject.dragSource); if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { // 拖动到垃圾箱中进行删除 dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); } else { drop(dragLayerX, dragLayerY); } } // 拖放结束 endDrag(); break; case MotionEvent.ACTION_CANCEL: mHandler.removeCallbacks(mScrollRunnable); cancelDrag(); break; } return true; }
// 根据当前的位置处理移动事件 private void handleMoveEvent(int x, int y) { // 移动View,DragView继承至View,在方法move()中设置setTranslationX()、setTranslationY()即可移动view 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.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); mLastTouch[0] = x; mLastTouch[1] = y; checkScrollState(x, y); // 对拖动时的翻页进行判断处理 }
// 检查拖动时的状态 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;// 更新前一次记录的拖拽目的对象为当前拖拽目的对象 }
// 对拖动时的翻页进行判断处理 private 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 boolean isRtl = (dragLayer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); final int forwardDirection = isRtl ? SCROLL_RIGHT : SCROLL_LEFT; final int backwardsDirection = isRtl ? 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(); } }
当用户将被拖拽物移动到相应位置后,可以将手指从屏幕上移开。此时,将在onInterceptTouchEvent与onTouchEvent中调用drop方法释放被拖拽物。其主要功能,就是查找拖拽目的对象(DropTarget),若找到且接受释放,通知该对象被拖拽物的放入。最后,通知拖拽源(被拖拽物最初所在的容器)拖拽结果。
case MotionEvent.ACTION_UP: // Ensure that we've processed a move event at the current pointer location. handleMoveEvent(dragLayerX, dragLayerY); mHandler.removeCallbacks(mScrollRunnable); if (mDragging) { // 判断是否到达可删除的区域 PointF vec = isFlingingToDelete(mDragObject.dragSource); if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { // 拖动到垃圾箱中进行删除 dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); } else { drop(dragLayerX, dragLayerY); } } // 拖放结束 endDrag(); break;先判断是否在删除区域,如果在并且可以删除就将该图标删除,否则进行drop处理,及释放拖拽物到当前位置,
// 释放被拖拽物到当前位置 private void drop(float x, float y) { final int[] coordinates = mCoordinatesTemp; // x,y所在区域是否有合适的目标 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); }
// 取得当前的具体的DropTarget对象 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; }
DropTarget.onDrop
这个方法最终将拖拽对象放置到目标位置,Workspace实现该方法,
// 拖拽对象被放置到目标位置 public void onDrop(final DragObject d) { mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, mDragViewVisualCenter);// 计算拖动View的视觉中心 CellLayout dropTargetLayout = mDropToLayout;// Drop的Celllayout对象 // We want the point to be mapped to the dragTarget. // 判断当前是否在Hotseat上,求出相对于dropTargetLayout的视觉中心坐标 if (dropTargetLayout != null) { if (mLauncher.isHotseatLayout(dropTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); } } int snapScreen = -1; boolean resizeOnDrop = false; // 如果DragObject-dragSource!= Worspace,转而调用onDropExternal(),否则继续处理onDrop()的内容 if (d.dragSource != this) { final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1] }; onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); } else if (mDragInfo != null) { final View cell = mDragInfo.cell; Runnable resizeRunnable = null; if (dropTargetLayout != null && !d.cancelled) { // Move internally boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); long container = hasMovedIntoHotseat ? LauncherSettings.Favorites.CONTAINER_HOTSEAT : LauncherSettings.Favorites.CONTAINER_DESKTOP; long screenId = (mTargetCell[0] < 0) ? mDragInfo.screenId : getIdForScreen(dropTargetLayout); int spanX = mDragInfo != null ? mDragInfo.spanX : 1; int spanY = mDragInfo != null ? mDragInfo.spanY : 1; // First we find the cell nearest to point at which the item is // dropped, without any consideration to whether there is an item there. mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); // If the item being dropped is a shortcut and the nearest drop // cell also contains a shortcut, then create a folder with the two shortcuts. // 如果拖拽的对象是一个快捷图标并且最近的位置上也是一个快捷图标,就创建一个文件夹来防止这两个图标 if (!mInScrollArea && createUserFolderIfNecessary(cell, container, dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { return; } // 添加到已存在的文件夹上 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, distance, d, false)) { return; } // Aside from the special case where we're dropping a shortcut onto a shortcut, // we need to find the nearest cell location that is vacant ItemInfo item = (ItemInfo) d.dragInfo; int minSpanX = item.spanX; int minSpanY = item.spanY; if (item.minSpanX > 0 && item.minSpanY > 0) { minSpanX = item.minSpanX; minSpanY = item.minSpanY; } //如果不满足文件夹的条件,则调用CellLayout-performReorder方法,这个方法就是处理拖动图标时,如果当前落点被占据时,挤开当前图标的效果 int[] resultSpan = new int[2]; mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; // if the widget resizes on drop if (foundCell && (cell instanceof AppWidgetHostView) && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { resizeOnDrop = true; item.spanX = resultSpan[0]; item.spanY = resultSpan[1]; AppWidgetHostView awhv = (AppWidgetHostView) cell; AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], resultSpan[1]);// AppWidget可能在拖动时发生缩小,因此会调用AppWidgetResizeFrame-updateWidgetSizeRanges方法 } // 拖动时可能落点在别的页面,所以还会有页面滑动的效果 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) { snapScreen = getPageIndexForScreenId(screenId); snapToPage(snapScreen); } // 如果满足则更新位置,保存新的位置信息到数据库中,播放动画效果,否则弹回原来位置 if (foundCell) { final ItemInfo info = (ItemInfo) cell.getTag(); if (hasMovedLayouts) { // Reparent the view CellLayout parentCell = getParentCellLayoutForView(cell); if (parentCell != null) { parentCell.removeView(cell); } else if (LauncherAppState.isDogfoodBuild()) { throw new NullPointerException("mDragInfo.cell has null parent"); } addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX, info.spanY); } // update the item's position after drop CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); lp.cellX = lp.tmpCellX = mTargetCell[0]; lp.cellY = lp.tmpCellY = mTargetCell[1]; lp.cellHSpan = item.spanX; lp.cellVSpan = item.spanY; lp.isLockedToGrid = true; if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && cell instanceof LauncherAppWidgetHostView) { final CellLayout cellLayout = dropTargetLayout; // We post this call so that the widget has a chance to be placed // in its final location final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); if (pinfo != null && pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { final Runnable addResizeFrame = new Runnable() { public void run() { DragLayer dragLayer = mLauncher.getDragLayer(); dragLayer.addResizeFrame(info, hostView, cellLayout); } }; resizeRunnable = (new Runnable() { public void run() { if (!isPageMoving()) { addResizeFrame.run(); } else { mDelayedResizeRunnable = addResizeFrame; } } }); } } LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX, lp.cellY, item.spanX, item.spanY);// 更新数据库 } else { // If we can't find a drop location, we return the item to its original position CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); mTargetCell[0] = lp.cellX; mTargetCell[1] = lp.cellY; CellLayout layout = (CellLayout) cell.getParent().getParent(); layout.markCellsAsOccupiedForView(cell); } } final CellLayout parent = (CellLayout) cell.getParent().getParent(); final Runnable finalResizeRunnable = resizeRunnable; // Prepare it to be animated into its new position // This must be called after the view has been re-parented final Runnable onCompleteRunnable = new Runnable() { @Override public void run() { mAnimatingViewIntoPlace = false; updateChildrenLayersEnabled(false); if (finalResizeRunnable != null) { finalResizeRunnable.run(); } } }; mAnimatingViewIntoPlace = true; if (d.dragView.hasDrawn()) { final ItemInfo info = (ItemInfo) cell.getTag(); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : ANIMATE_INTO_POSITION_AND_DISAPPEAR; animateWidgetDrop(info, parent, d.dragView, onCompleteRunnable, animationType, cell, false); } else { int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, onCompleteRunnable, this); } } else { d.deferDragViewCleanupPostAnimation = false; cell.setVisibility(VISIBLE); } parent.onDropChild(cell); } }