** Luancher3的拖拽流程实现,实际上触发的是View的长按事件,长按事件的处理在Launcher.java中的onLongClick方法中,进行实现**
- 在Launcher3中有三种长按拖拽处理:
①主屏幕上的图标和小部件
②文件夹中的图标
③抽屉中的图标和小部件 - 一、长按处理中的onLongClick(View v)处理
在Cellinfo中的信息:
View cell;//当前长按的view
int cellX = -1;//所在位置x
int cellY = -1;//所在位置y
int spanX;//x方向占格子数
int spanY;//y方向占格子数
long screenId;//页面数
long container;//容器类型
二、在实现完成后会进入Workspace中执行startDrag中:
public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
View child = cellInfo.cell;//
mDragInfo = cellInfo;
child.setVisibility(INVISIBLE);
if (options.isAccessibleDrag) {
mDragController.addDragListener(
new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
@Override
protected void enableAccessibleDrag(boolean enable) {
super.enableAccessibleDrag(enable);
setEnableForLayout(mLauncher.getHotseat(), enable);
}
});
}
beginDragShared(child, this, options);
}
- 在start中调用了beginDragShared方法:
public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
float iconScale = 1f;
//当前的应用应用是不是BulleTextView
if (child instanceof BubbleTextView) {
Drawable icon = ((BubbleTextView) child).getIcon();
if (icon instanceof FastBitmapDrawable) {
iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
}
}
// 清除按下的状态
child.clearFocus();
child.setPressed(false);
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedBackground();
}
mOutlineProvider = previewProvider;
if (draggableView == null && child instanceof DraggableView) {
draggableView = (DraggableView) child;
}
final View contentView = previewProvider.getContentView();
final float scale;
// 建立拖动设备的触点
final Drawable drawable;
if (contentView == null) {
drawable = previewProvider.createDrawable();
scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
} else {
drawable = null;
scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
}
int halfPadding = previewProvider.previewPadding / 2;
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
Point dragVisualizeOffset = null;
Rect dragRect = new Rect();
if (draggableView != null) {
draggableView.getSourceVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
}
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
}
if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
PopupContainerWithArrow popupContainer = PopupContainerWithArrow
.showForIcon((BubbleTextView) child);
if (popupContainer != null) {
dragOptions.preDragCondition = popupContainer.createPreDragCondition();
}
}
final DragView dv;
//建立拖拽的视图
if (contentView instanceof View) {
if (contentView instanceof LauncherAppWidgetHostView) {
mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
}
dv = mDragController.startDrag(
contentView,
draggableView,
dragLayerX,
dragLayerY,
source,
dragObject,
dragVisualizeOffset,
dragRect,
scale * iconScale,
scale,
dragOptions);
} else {
dv = mDragController.startDrag(
drawable,
draggableView,
dragLayerX,
dragLayerY,
source,
dragObject,
dragVisualizeOffset,
dragRect,
scale * iconScale,
scale,
dragOptions);
}
return dv;
}
在该方法中主要实现了创建拖拽视图,通风了DragController的startDrag方法来创建
DragController中的startDrag方法:
//该方法位于dragndrop中的DragController中
public DragView startDrag(
Drawable drawable,
DraggableView originalView,
int dragLayerX,
int dragLayerY,
DragSource source,
ItemInfo dragInfo,
Point dragOffset,
Rect dragRegion,
float initialDragViewScale,
float dragViewScaleOnDrop,
DragOptions options) {
return startDrag(drawable, /* view= */ null, originalView, dragLayerX, dragLayerY,
source, dragInfo, dragOffset, dragRegion, initialDragViewScale, dragViewScaleOnDrop,
options);
}
在实现过程中先获取了位置,然后调用handleMoveEvent来处理拖拽的移动
private Point getClampedDragLayerPos(float x, float y) {
mActivity.getDragLayer().getLocalVisibleRect(mRectTemp);
mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1));
mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1));
return mTmpPoint;
}
//进行拖动调用handleMoveEvent
@Override
public void onDriverDragMove(float x, float y) {
Point dragLayerPos = getClampedDragLayerPos(x, y);
handleMoveEvent(dragLayerPos.x, dragLayerPos.y);
}
- 拖拽移动:
在其中使用 了move进行绘制更新的位置,调用checkTouchMove用于检测在哪个DropTarget,
protected void handleMoveEvent(int x, int y) {
mDragObject.dragView.move(x, y);//移动dragview在方法中科院设置setTranslationX,和setTranslationY
// Drop on someone?
final int[] coordinates = mCoordinatesTemp;
//根据坐标获取当前的dropTarget
DropTarget dropTarget = findDropTarget(x, y, coordinates);
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
//根据dragview在哪个dropTarget上面,检查拖动时的状态
checkTouchMove(dropTarget);
// Check if we are hovering over the scroll areas
mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
mLastTouch.set(x, y);
int distanceDragged = mDistanceSinceScroll;
if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
distanceDragged /= DEEP_PRESS_DISTANCE_FACTOR;
}
if (mIsInPreDrag && mOptions.preDragCondition != null
&& mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
callOnDragStart();
}
}
三、当进行触摸处理时
- 当进行触摸处理时
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
return mDragDriver != null && mDragDriver.onTouchEvent(ev);
}
- OnTouchevent:触摸处理函数,位于DragDrvier.java中
@Override
public boolean onTouchEvent(MotionEvent ev) {
mSecondaryEventConsumer.accept(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
break;
}
return true;
}
- 当离开屏幕时触发:ACTION_UP事件,在其中调用了onDriverDragMove和onDriverDragEnd方法,
在该方法中调用了drop函数和endDrag函数,两者都位于DragController.java中
@Override
public void onDriverDragEnd(float x, float y) {
if (!endWithFlingAnimation()) {
drop(findDropTarget((int) x, (int) y, mCoordinatesTemp), null);
}
endDrag();
}
- drop函数
protected void drop(DropTarget dropTarget, Runnable flingAnimation) {
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;
if (mIsInPreDrag) {
if (dropTarget != null) {
dropTarget.onDragExit(mDragObject);
}
return;
}
// Drop onto the target.
boolean accepted = false;
if (dropTarget != null) {
dropTarget.onDragExit(mDragObject);
if (dropTarget.acceptDrop(mDragObject)) {
if (flingAnimation != null) {
flingAnimation.run();
} else {
dropTarget.onDrop(mDragObject, mOptions);
}
accepted = true;
}
}
final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
dispatchDropComplete(dropTargetAsView, accepted);
}
在drop函数中调用了run函数和onDrop函数,分别是快速上滑时的调用和普通的拖拽调用。
- 先在DelteDropTarget.java中找到onDrop函数
@Override
public void onDrop(DragObject d, DragOptions options) {
if (canRemove(d.dragInfo)) {
mLauncher.getModelWriter().prepareToUndoDelete();
d.dragInfo.container = NO_ID;
}
super.onDrop(d, options);
mStatsLogManager.logger().withInstanceId(d.logInstanceId)
.log(mLauncherEvent);
}
- 进行空页面的,还有拖拽对象等的删除
@Override
public void onAccessibilityDrop(View view, ItemInfo item) {
// Remove the item from launcher and the db, we can ignore the containerInfo in this call
// because we already remove the drag view from the folder (if the drag originated from
// a folder) in Folder.beginDrag()
mLauncher.removeItem(view, item, true /* deleteFromDb */);
mLauncher.getWorkspace().stripEmptyScreens();
mLauncher.getDragLayer() .announceForAccessibility(getContext().getString(R.string.item_removed));
}
- 在worlspace.java中的ondrop方法
在该方法中计算了drop的位置,之后会判断是不是在另外一个图标上面。若是则创建文件夹;直接放入一个已经存在的文件夹中,在folder中实现了这ondrop中,拖拽对象是不是在另一页中,若是则调用addScreen方法,若没有在其他页就更新位置,最后再更新数据库,若是找不到位置则放在原来的位置
@Override
public void onDrop(final DragObject d, DragOptions options) {
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);//计算拖动View的视觉中心
CellLayout dropTargetLayout = mDropToLayout;//Drop的CellLayout对象
// We want the point to be mapped to the dragTarget.
//判断当前是否在Hotset上,求出相对dropTargetLayout的视觉中心坐标
if (dropTargetLayout != null) {
mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter);
}
boolean droppedOnOriginalCell = false;
int snapScreen = -1;
boolean resizeOnDrop = false;
//如果DragObject-dragSource!=Workspace,则调用onDropExternal,否则继续执行onDrop的内容
if (d.dragSource != this || mDragInfo == null) {
final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1] };
onDropExternal(touchXY, dropTargetLayout, d);
} else {
final View cell = mDragInfo.cell;
final DragView dragView = d.dragView;
boolean droppedOnOriginalCellDuringTransition = false;
Runnable onCompleteRunnable = dragView::resumeColorExtraction;
dragView.disableColorExtraction();
if (dropTargetLayout != null && !d.cancelled) {
// Move internally
boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
int container = hasMovedIntoHotseat ?
LauncherSettings.Favorites.CONTAINER_HOTSEAT :
LauncherSettings.Favorites.CONTAINER_DESKTOP;
int 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 (createUserFolderIfNecessary(cell, container,
dropTargetLayout, mTargetCell, distance, false, d)
|| addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
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 = d.dragInfo;
int minSpanX = item.spanX;
int minSpanY = item.spanY;
if (item.minSpanX > 0 && item.minSpanY > 0) {
minSpanX = item.minSpanX;
minSpanY = item.minSpanY;
}
droppedOnOriginalCell = item.screenId == screenId && item.container == container
&& item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState;
// When quickly moving an item, a user may accidentally rearrange their
// workspace. So instead we move the icon back safely to its original position.
boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
&& !droppedOnOriginalCellDuringTransition && !dropTargetLayout
.isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
//若不满足文件夹的条件,则调用performReorder方法处理拖动图标,若当前落点被占据,挤开当前图标
int[] resultSpan = new int[2];
if (returnToOriginalCellToPreventShuffling) {
mTargetCell[0] = mTargetCell[1] = -1;
} else {
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;
WidgetSizes.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
resultSpan[1]);//Appwight可能在拖动时会发生缩小,所以调用updateWidgetSizeRanges方法
}
if (foundCell) {
//拖动时可能落在别的页面,所以还是会有页面滑动效果
if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
snapScreen = getPageIndexForScreenId(screenId);
snapToPage(snapScreen);
}
//若满足更新。则保持新的位置信息到数据库中,播放动画效果。否则弹回去原来的位置中
final ItemInfo info = (ItemInfo) cell.getTag();
if (hasMovedLayouts) {
// Reparent the view
CellLayout parentCell = getParentCellLayoutForView(cell);
if (parentCell != null) {
parentCell.removeView(cell);
} else if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
d.dragView.detachContentView(/* reattachToPreviousParent= */ false);
} else if (FeatureFlags.IS_STUDIO_BUILD) {
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
&& !options.isAccessibleDrag) {
final Runnable previousRunnable = onCompleteRunnable;
onCompleteRunnable = () -> {
previousRunnable.run();
if (!isPageInTransition()) {
AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
}
};
}
}
mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
lp.cellX, lp.cellY, item.spanX, item.spanY);//更新数据库信息
} else {
if (!returnToOriginalCellToPreventShuffling) {
onNoCellFound(dropTargetLayout);
}
if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
}
// 当找不到放置位置时会将其放回到原始位置去
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
mTargetCell[0] = lp.cellX;
mTargetCell[1] = lp.cellY;
CellLayout layout = (CellLayout) cell.getParent().getParent();
layout.markCellsAsOccupiedForView(cell);
}
} else {
// 当取消拖动后,则又会重新回到原来的 父视图中去
if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
}
}
final CellLayout parent = (CellLayout) cell.getParent().getParent();
if (d.dragView.hasDrawn()) {
if (droppedOnOriginalCellDuringTransition) {
// Animate the item to its original position, while simultaneously exiting
// spring-loaded mode so the page meets the icon where it was picked up.
final RunnableList callbackList = new RunnableList();
final Runnable onCompleteCallback = onCompleteRunnable;
mLauncher.getDragController().animateDragViewToOriginalPosition(
/* onComplete= */ callbackList::executeAllAndDestroy, cell,
SPRING_LOADED.getTransitionDuration(mLauncher));
mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
onCompleteCallback == null
? null
: forSuccessCallback(
() -> callbackList.add(onCompleteCallback)));
mLauncher.getDropTargetBar().onDragEnd();
parent.onDropChild(cell);
return;
}
final ItemInfo info = (ItemInfo) cell.getTag();
boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
if (isWidget) {
int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
ANIMATE_INTO_POSITION_AND_DISAPPEAR;
animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
} else {
int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
this);
}
} else {
d.deferDragViewCleanupPostAnimation = false;
cell.setVisibility(VISIBLE);
}
parent.onDropChild(cell);
mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY,
forSuccessCallback(onCompleteRunnable));
mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
.log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
}
if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
d.stateAnnouncer.completeAction(R.string.item_moved);
}
}
该方法执行完后会执行workspace中的onDragEnd方法
@Override
public void onDragEnd() {
if (ENFORCE_DRAG_EVENT_ORDER) {
enforceDragParity("onDragEnd", 0, 0);
}
updateChildrenLayersEnabled();
StateManager<LauncherState> stateManager = mLauncher.getStateManager();
stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == NORMAL) {
if (!mDeferRemoveExtraEmptyScreen) {
removeExtraEmptyScreen(true /* stripEmptyScreens */);
}
stateManager.removeStateListener(this);
}
}
});
mDragInfo = null;
mOutlineProvider = null;
mDragSourceInternal = null;
}