本文来自http://blog.csdn.net/chenshaoyang0011 转载请申明文章出处!
通过上一篇文章Android4.0Launcher2源码分析(五)——Workspace的滑动中,已经了解了Launcher的ViewTree中各层所负责的工作,在DragLayer中就负责对快捷图标和AppWidget等组件的拖拽工作。桌面的滑动和图标的拖拽是两项独立的工作,正常情况下我们用手指滑动桌面会触发滑动操作,而当长按一个图标时,则会触发图标的拖拽操作,此时再滑动则会拖拽图标移动而桌面不会滑动。那么这里就分两大部分来探讨:1、拖拽操作的启动。2、拖拽。
一、拖拽操作的启动
那么首先进入Launcher.onCreate()中来探究下如何激活拖拽的状态。
- protected void onCreate(Bundle savedInstanceState) {
- ......
- setupViews();
- ......
- }
接着进入setupViews();
- private void setupViews() {
- ......
- mWorkspace.setOnLongClickListener(this);
- ......
- }
从这里我们可以看到对Workspace设置了OnLongClickListener,而Launcher又实现了这个接口。接着进入Launcher.onLongClick()
- public boolean onLongClick(View v) {
- ......
- if (!(v instanceof CellLayout)) {
- v = (View) v.getParent().getParent();
- }
- resetAddInfo();
- CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
- ......
-
-
- final View itemUnderLongClick = longClickCellInfo.cell;
- boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
- if (allowLongPress && !mDragController.isDragging()) {
- if (itemUnderLongClick == null) {
- ......
- } else {
- if (!(itemUnderLongClick instanceof Folder)) {
-
- mWorkspace.startDrag(longClickCellInfo);
- }
- }
- }
- return true;
- }
当用户在一个item上长按时,则itemUnderLongClick != null,再通过调用Workspace.startDrag()来激活item的拖拽。下面先通过时序图来看下拖拽状态激活所经历的过程:
图标拖拽功能的激活大概可以分为六步,下面就一步一步的探究下其中的实现:
Step1:Workspace.startDrag(CellLayout.CellInfo cellInfo)
- void startDrag(CellLayout.CellInfo cellInfo) {
- View child = cellInfo.cell;
- ......
- mDragInfo = cellInfo;
-
- child.setVisibility(GONE);
- ......
- final Canvas canvas = new Canvas();
-
-
- final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
-
-
-
- mDragOutline = createDragOutline(child, canvas, bitmapPadding);
- beginDragShared(child, this);
- }
在这个方法中,主要的工作就是让图标从桌面上消失,并且显示一个图标的外部轮廓,以表明它将要放置的位置,其显示的效果如下:
显示图标的轮廓可以从视觉上给用户更加好的体验。接着,进入beginDragShared()
Step2:Workspace.beginDragShared(View child,DragSource source)
- public void beginDragShared(View child, DragSource source) {
- ......
-
- final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);
- final int bmpWidth = b.getWidth();
-
-
-
- mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
- final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
- int dragLayerY = mTempXY[1] - bitmapPadding / 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;
-
-
- dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 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);
- }
- ......
- mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
- b.recycle();
- }
Workspace.beginSharedDrag()中主要所做的工作就是计算拖拽目标位于DragLayer中的坐标和尺寸大小,接着又调用DragController.startDrag()
Step3:DragController.startDrag(Bitmap b ,int dragLayerX, int dragLayerY,DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
- DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
- ......
- 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;
- ......
- final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
- registrationY, 0, 0, b.getWidth(), b.getHeight());
- ......
-
- dragView.show(mMotionDownX, mMotionDownY);
- handleMoveEvent(mMotionDownX, mMotionDownY);
- }
代码中显示通过一个for语句调用了DragListener.onDragStart()方法,通知它们已经开始拖拽了,其中由于Workspace实现了DragListener并且添加到了mListeners中。所以Workspace.onDragStart()被调用。然后又封装了一个DragObject对象,封装DragSource、DragInfo和DragView等信息。接着,将调用DragView.show()将DragView显示在DragLayer中。
Step4:DragView.show(int touchX,int touchY)
-
-
-
-
-
-
- public void show(int touchX, int touchY) {
-
- mDragLayer.addView(this);
-
-
- DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
- lp.width = mBitmap.getWidth();
- lp.height = mBitmap.getHeight();
- lp.x = touchX - mRegistrationX;
- lp.y = touchY - mRegistrationY;
- lp.customPosition = true;
- setLayoutParams(lp);
- mLayoutParams = lp;
- mAnim.start();
- }
其中的内容很简单易懂,就是在将DragView添加到了DragLayer中,并且在合适的位置显示了出来。接着应该调用在DragController.startDrag()中调用handleMoveEvent(),这个将在后文将拖拽过程分析时在看。到这一步,拖拽操作的启动过程就完成了。接着就可以拖拽图标了。
二、拖拽
通过了前面文章的分析,已经知道了拖拽过程的实现在DragLayer中,当进行图标的拖拽时,DragLayer.onInterceptTouchEvent()就会对MotionEvent进行拦截。并且
在自身的onTouchEvent()方法中进行操作,从而实现图标的移动。由于onInterceptTouchEvent()拦截了MotionEvent,因此Workspace等UI控件不会接收到事件,从而不会产生
干扰。那么首先进入DragLayer.onInterceptTouchEvent():
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- ......
- return mDragController.onInterceptTouchEvent(ev);
- }
代码中省略了与其他功能的不部分代码,最后调用了DragController.onInterceptTouchEvent() ,并取其返回值作为自身方法的返回值。进入DragController.onInterceptTouchEvent()。
-
-
-
- public boolean onInterceptTouchEvent(MotionEvent 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:
-
- mMotionDownX = dragLayerX;
- mMotionDownY = dragLayerY;
- mLastDropTarget = null;
- break;
- case MotionEvent.ACTION_UP:
- if (mDragging) {
- drop(dragLayerX, dragLayerY);
- }
- endDrag();
- break;
- case MotionEvent.ACTION_CANCEL:
- cancelDrag();
- break;
- }
-
- return mDragging;
- }
这里我们关心的是它的返回值。可以看到方法将mDragging作为返回值。当触发了拖拽状态,在的DragController.startDrag()中将mDragging的值改为true。所以这里也将返回true。DragLayer将拦截MotionEvent,并传给自身的onTouchEvent()方法,在onTouchEvent()中对图标进行移动,刷新界面。
-
-
-
- public boolean onTouchEvent(MotionEvent 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:
-
- 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) {
- <span style="white-space:pre"> </span>
- drop(dragLayerX, dragLayerY);
- }
- endDrag();
- break;
- case MotionEvent.ACTION_CANCEL:
- cancelDrag();
- break;
- }
- return true;
- }
onTouchEvent()中处理的事件涉及到不同状态之间的转换,以及每种状态之下对相应的MotionEvent的对策。这里同样,从简单的情况入手:图标拖拽起来后,移动一段距离,在屏幕的另一个位置放下。
首先,当拖拽起图标时,拖拽图标的状态被启动,这就是第一部分所探讨的内容。
然后,移动拖拽的图标。此时触发了MotionEvent.ACTION_MOVE事件,紧接着调用handleMoveEvent()来处理移动。进入handleMoveEvent()来看看图标移动是怎么实现的。
- 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;
- }
-
- if (mLastDropTarget != dropTarget) {
- if (mLastDropTarget != null) {
-
- mLastDropTarget.onDragExit(mDragObject);
- }
-
- dropTarget.onDragEnter(mDragObject);
- }
- dropTarget.onDragOver(mDragObject);
- } else {
- if (mLastDropTarget != null) {
- mLastDropTarget.onDragExit(mDragObject);
- }
- }
- mLastDropTarget = dropTarget;
-
-
- boolean inDeleteRegion = false;
- if (mDeleteRegion != null) {
- inDeleteRegion = mDeleteRegion.contains(x, y);
- }
-
-
-
- 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;
-
-
-
- if (!inDeleteRegion && x < mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
- mScrollRunnable.setDirection(SCROLL_LEFT);
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
- }
- }
- } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
- mScrollRunnable.setDirection(SCROLL_RIGHT);
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
- }
- }
- } else {
- if (mScrollState == SCROLL_WAITING_IN_ZONE) {
- mScrollState = SCROLL_OUTSIDE_ZONE;
- mScrollRunnable.setDirection(SCROLL_RIGHT);
- mHandler.removeCallbacks(mScrollRunnable);
- mDragScroller.onExitScrollArea();
- }
- }
- }
handleMoveEvent()主要处理拖拽过程中需要处理的事务。包括:1、在更新图标在屏幕中的位置,并刷新UI。2、判断图标当前所处的位置。包括SCROLL_OUTSIDE_ZONE和SCROLL_WAITING_IN_ZONE,对处于SCROLL_WAITING_IN_ZONE位置时,需要根据具体的位置,向前或向后切换显示的屏幕。再回到上面假设的情况中。则此时只是简单的刷新了位置信息,并重新绘制图标。
最后,当松开拖拽的对象时,触发了MotionEvent.ACTION_UP事件。则进入下面一段代码:
- <span style="white-space:pre"> </span>
- handleMoveEvent(dragLayerX, dragLayerY);
-
- mHandler.removeCallbacks(mScrollRunnable);
- if (mDragging) {
- drop(dragLayerX, dragLayerY);
- }
- endDrag();
先调用handleMoveEvent()确保已经完成了位置移动的操作。接下来调用mHandler.removeCallbacks(mScrollRunnalbe)取消可能存放在消息队列中的滑动任务。接着调用drop(dragLayerX,dragLayerY)将拖拽的对象放置合适的DropTarget对象中(如Workspace,Folder)。
- private void drop(float x, float y) {
- ......
-
- final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
-
- ......
- 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, accepted);
- }
最后调用endDrag()结束拖拽过程。
- private void endDrag() {
- if (mDragging) {
-
- mDragging = false;
- for (DragListener listener : mListeners) {
-
- listener.onDragEnd();
- }
- if (mDragObject.dragView != null) {
-
- mDragObject.dragView.remove();
- mDragObject.dragView = null;
- }
- }
- }
至此,拖拽的图标的过程就结束了。