Launcher的长按—移动—松开
一、滑动
在上一篇文章代码流程中 addInScreen’中添加了长按监听器。给图标设置的监听器是Launcher对象,他实现了onLongClick方法。
接着之前的代码:
ItemLongClickListener.java
public static OnLongClickListener INSTANCE_WORKSPACE =
ItemLongClickListener::onWorkspaceItemLongClick;
private static boolean onWorkspaceItemLongClick(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
return true;
}
public static void beginDrag(View v, Launcher launcher, ItemInfo info,
DragOptions dragOptions) {
CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
}
Workspace.java
public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
View child = cellInfo.cell;
//点击的图标设置为不可见
child.setVisibility(INVISIBLE);
beginDragShared(child, this, options);
}
public void beginDragShared(View child, DragSource source, DragOptions options) {
Object dragObject = child.getTag();
beginDragShared(child, source, (ItemInfo) dragObject, new DragPreviewProvider(child), options);
}
public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
DragPreviewProvider previewProvider, DragOptions dragOptions) {
//将点击的图标放弃焦点,按下状态设置为false
child.clearFocus();
child.setPressed(false);
// The drag bitmap follows the touch point around on the screen,生成随手动的bitmap
final Bitmap b = previewProvider.createDragBitmap();
if (child instanceof BubbleTextView) {
dragRect = new Rect(); //dragRect用于计算拖动层的偏移量
((BubbleTextView) child).getIconBounds(dragRect);
dragLayerY += dragRect.top;
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
} else if (child instanceof FolderIcon) {
//...
} else if (previewProvider instanceof ShortcutDragPreviewProvider) {
//...
}
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
return dv;
}
DragController.java
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
mDragObject = new DropTarget.DragObject(); //包含拖拽时的信息
//1.通过上面的bitmap生成跟随手移动的View
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
dragView.setItemInfo(dragInfo);
//标识在拖拽中
mDragObject.dragComplete = false;
//将View显示在某个位置上(滑动的时候这个坐标一直变)
dragView.show(mMotionDownX, mMotionDownY);
//2.生成动画
callOnDragStart();
//3.处理滑动事件ACTION_MOVE
handleMoveEvent(mMotionDownX, mMotionDownY);
return dragView;
}
private void callOnDragStart() {
//拖拽监听器
for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragStart(mDragObject, mOptions);//此后开始接收滑动触摸的消息了
}
}
WorkSpace.java
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
// Always enter the spring loaded mode
mLauncher.getStateManager().goToState(SPRING_LOADED);
}
LauncherStateManager.java
private void goToState(LauncherState state, boolean animated, long delay,
final Runnable onCompleteRunnable) {
//生成动画
AnimatorSet animation = createAnimationToNewWorkspaceInternal(
state, builder, onCompleteRunnable);
Runnable runnable = new StartAnimRunnable(animation);
mUiHandler.post(runnable);
}
二、触摸
DropTarget是一个可放置(drop)区域的抽象,也就是我们松开手的时候想要把图标放到某个东西上,这个东西就是DropTarget,实现他的都是View,比如说文件夹,Workspace,删除区等等
DropTarget.java
- onDragEnter是当拖动图标到某一DropTarget(蓝色),边缘,刚刚进入DropTarget范围内的时候所调用的内容。比如拖动桌面的一个快捷方式到桌面顶端的删除区域,“删除”两字和手中的图标会变红
- onDragOver是在某一DropTarget内部移动的时候会调用的回调,比如我们把手上的图标移动到两个图标中间的时候,会发生挤位的情况(就是桌面已有图标让出空位),基本上每个ACTION_MOVE操作都会调用他。
- onDragExit是从某一DropTarget拖出时候会进行的回调,比如onDragEnter时变红的“删除”和图标会在这个调用中恢复正常。
- onDrop是松手时候发生的调用,做一些放下时候的操作,比如删除快捷方式的时候会在onDrop里面开始删除的操作。
DragController.java
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];
//检测进入某个dropTarget,其实现上上图的方法
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); //检测是否滑动到下一页
if (mIsInPreDrag && mOptions.preDragCondition != null
&& mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
callOnDragStart();
}
}
DragLayer.java
@Override
public boolean onTouchEvent(MotionEvent ev) {
//drgalayer的ontouchevent被DragController接管
return mActiveController.onControllerTouchEvent(ev);
}
DragController.java, DragController implements DragDriver.EventListener
private DragDriver mDragDriver = null;
public boolean onControllerTouchEvent(MotionEvent ev) {
return mDragDriver.onTouchEvent(ev); //调到了DragDriver
}
DragDriver.java abstract类
public boolean onTouchEvent(MotionEvent 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;
}
DragController.java DragController implements DragDriver.EventListener
@Override
public void onDriverDragMove(float x, float y) {
final int[] dragLayerPos = getClampedDragLayerPos(x, y);
handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); //处理移动
}
@Override
public void onDriverDragEnd(float x, float y) { //手指抬起来时触发ACTION_UP,所以执行此方法
DropTarget dropTarget;
Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
if (flingAnimation != null) { //判断是不是在扔图标
dropTarget = mFlingToDeleteHelper.getDropTarget();
} else {
dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
}
//找到dropTarget,调用下面两个方法
drop(dropTarget, flingAnimation);
endDrag();
}
DragController.java
private void drop(DropTarget dropTarget, Runnable flingAnimation) {
mDragObject.dragComplete = true;
if (dropTarget.acceptDrop(mDragObject)) { //比如放在已经满的hotseat,就不accept
dropTarget.onDrop(mDragObject, mOptions); //具体的droptarget中的drop
}
dispatchDropComplete(dropTargetAsView, accepted); //drop 完成删除view等
}
DragController.java
private void dispatchDropComplete(View dropTarget, boolean accepted) {
mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted);
}
WorkSpace.java
//onDrop代码内容非常复杂
public void onDrop(final DragObject d, DragOptions options) {
//经过一系列计算得到mTargetCell,也就是要drop的位置
mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
//是不是在另一个图标上,是就创建文件夹,或者直接放进文件夹
if (createUserFolderIfNecessary(cell, container,
dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
return;
}
//经过一系列计算,判断拖拽对象是不是在另一页,是的话就addInScreen
if (foundCell) {
if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
snapScreen = getPageIndexForScreenId(screenId);
snapToPage(snapScreen);
}
addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
info.spanX, info.spanY);
}
// 如果没有在其他页就更新位置更新数据库
mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
lp.cellX, lp.cellY, item.spanX, item.spanY);
//找不到位置就放回原位置
layout.markCellsAsOccupiedForView(cell);
}
}
mLauncher.getDropTargetBar().onDragEnd(); //最后dropend
}
WorkSpace.java
public void onDropCompleted(final View target, final DragObject d,final boolean success) {
if (success) {
removeWorkspaceItem(mDragInfo.cell); //删除拖拽对象和view
}
View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
cell.setVisibility(VISIBLE); //设置view可见
mDragInfo = null; //将拖拽信息置为null
}
DragController.java
//接上面,dorp完了就是endDrag了
private void endDrag() {
mDragObject.dragView = null; //清理dragview
callOnDragEnd();
//释放VelocityTracker,在drop里面 用来检查是否在扔的动作计算,计算速度,x方向,y方向
mFlingToDeleteHelper.releaseVelocityTracker();
}
private void callOnDragEnd() {
for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragEnd();
}
}
参考文章
https://fookwood.com/launcher-drag-up
https://fookwood.com/launcher-drag-move