launcher5.0-拖动讲解
图标拖动过程先长按,拖动,松开手指
launcher.java中setupViews方法,
private void setupViews() { final DragController dragController = mDragController; mLauncherView = findViewById(R.id.launcher); mDragLayer = (DragLayer) findViewById(R.id.drag_layer); mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); mQsbDivider = findViewById(R.id.qsb_divider); mDockDivider = findViewById(R.id.dock_divider); mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg); // Setup the drag layer mDragLayer.setup(this, dragController); // Setup the hotseat mHotseat = (Hotseat) findViewById(R.id.hotseat); if (mHotseat != null) { mHotseat.setup(this); } // Setup the workspace mWorkspace.setHapticFeedbackEnabled(false); mWorkspace.setOnLongClickListener(this); mWorkspace.setup(dragController); dragController.addDragListener(mWorkspace); // Get the search/delete bar mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar); // Setup AppsCustomize mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane); mAppsCustomizeContent = (AppsCustomizePagedView) mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); mAppsCustomizeContent.setup(this, dragController); // Setup the drag controller (drop targets have to be added in reverse order in priority) dragController.setDragScoller(mWorkspace); dragController.setScrollView(mDragLayer); dragController.setMoveTarget(mWorkspace); dragController.addDropTarget(mWorkspace); if (mSearchDropTargetBar != null) { mSearchDropTargetBar.setup(this, dragController); } }
mWorkspace.setOnLongClickListener(this); 看 onLongClick 方法
public boolean onLongClick(View v) { if (!isDraggingEnabled()) return false; if (isWorkspaceLocked()) return false; if (mState != State.WORKSPACE) return false; if (!(v instanceof CellLayout)) { v = (View) v.getParent().getParent(); } resetAddInfo(); CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); // This happens when long clicking an item with the dpad/trackball if (longClickCellInfo == null) { return true; } // The hotseat touch handling does not go through Workspace, and we always allow long press // on hotseat items. final View itemUnderLongClick = longClickCellInfo.cell; boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress(); if (allowLongPress && !mDragController.isDragging()) { if (itemUnderLongClick == null) { // User long pressed on empty space mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); startWallpaper(); } else { if (!(itemUnderLongClick instanceof Folder)) { // User long pressed on an item mWorkspace.startDrag(longClickCellInfo); } } } return true; }
如上:longClickCellInfo 在哪个cell中长按(桌面page或者hotseatlayout) ,itemUnderLongClick 是 被长按的view
如果 itemUnderLongClick 为空则长按空白区域 则执行startWallpaper()换壁纸,如果为非文件夹item(如app或者快捷方式)则执行
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); child.clearFocus(); child.setPressed(false); final Canvas canvas = new Canvas(); // The outline is used to visualize where the item will land if dropped mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); beginDragShared(child, this); }
createDragOutline(child, canvas, DRAG_BITMAP_PADDING) 长按后创建移动的投影边框
beginDragShared(child, this)开始拖拽移动
public void beginDragShared(View child, DragSource source) { Resources r = getResources(); // The drag bitmap follows the touch point around on the screen final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_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 - DRAG_BITMAP_PADDING / 2); Point dragVisualizeOffset = null; Rect dragRect = null; if (child instanceof BubbleTextView || child instanceof PagedViewIcon) { int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top); 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(-DRAG_BITMAP_PADDING / 2, iconPaddingTop - DRAG_BITMAP_PADDING / 2); dragRect = new Rect(left, top, right, bottom); } else if (child instanceof FolderIcon) { int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size); dragRect = new Rect(0, 0, child.getWidth(), previewSize); } // Clear the pressed state if necessary if (child instanceof BubbleTextView) { BubbleTextView icon = (BubbleTextView) child; icon.clearPressedOrFocusedBackground(); } mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale); b.recycle(); // Show the scrolling indicator when you pick up an item showScrollingIndicator(false); }
final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);创建了图标图片
float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);获取scale图标缩放比和mTempXY所方比后的左上角坐标
dragLayerX,dragLayerY 拖动图标的显示位置
执行
mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
交给mDragController.startDrag去执行拖动
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"); } // 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; 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); }
生成当前的拖动mDragObject对象 含有拖动信息 如dragInfo 坐标信息
mMotionDownX,mMotionDownY 手指按下的坐标
dragView.show(mMotionDownX, mMotionDownY) 显示拖动view
其他 图标移位,图标晃动等交给 handleMoveEvent(mMotionDownX, mMotionDownY)处理 ,handleMoveEvent在onTouchEvent中也有调用,拖动中不断调用
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);
看下方法 handleMoveEvent
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.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); mLastTouch[0] = x; mLastTouch[1] = y; checkScrollState(x, y); }
先分析DropTarget类
public interface DropTarget { ...
void onDrop(DragObject dragObject); void onDragEnter(DragObject dragObject); void onDragOver(DragObject dragObject); void onDragExit(DragObject dragObject); ... }
- onDragEnter是当拖动图标到某一DropTarget(蓝色),边缘,刚刚进入DropTarget范围内的时候所调用的内容。比如说我们拖动桌面的一个快捷方式,到桌面顶端的删除区域,“删除”两字和手中的图标会变红,这些动作都是在onDragEnter回调中完成的
- onDragOver是在某一DropTarget内部移动的时候会调用的回调,比如我们把手上的图标移动到两个图标中间的时候,会发生挤位的情况(就是桌面已有图标让出空位),基本上每个ACTION_MOVE操作都会调用他。
- onDragExit是从某一DropTarget拖出时候会进行的回调,比如onDragEnter时变红的“删除”和图标会在这个调用中恢复正常。
- onDrop是松手时候发生的调用,做一些放下时候的操作,比如删除快捷方式的时候会在onDrop里面开始删除的操作。
右键 open Type Hierchy 显示 继承DropTarget 三个类 分别是 ButtonDropTarge(长按后显示的删除区域) Folder(文件夹) Workspace (桌面区域)
接着分析handleMoveEvent
int x, int y 是当前手指拖动坐标
DropTarget dropTarget = findDropTarget(x, y, coordinates); 表示拖动到删除区域,文件夹还是在左面工作区
checkTouchMove(dropTarget);函数
private void checkTouchMove(DropTarget dropTarget) { if (dropTarget != null) { DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); if (delegate != null) { dropTarget = delegate; } if (mLastDropTarget != dropTarget) { if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); } dropTarget.onDragEnter(mDragObject); } dropTarget.onDragOver(mDragObject); } else { if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); } } mLastDropTarget = dropTarget; }
设置各种效果 具体在DropTarget实现类中
checkScrollState 检查是否拖动到桌面边缘 触法Scroll效果
这就是拖动过程 接下来看手指松开代码 DragControler onTouchEvent
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 (vec != null) { dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); } else { drop(dragLayerX, dragLayerY); } } endDrag(); break;
PointF vec = isFlingingToDelete(mDragObject.dragSource);是否是删除图标
删除图标执行dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
否则执行drop(dragLayerX, dragLayerY);