Android 4.0 Launcher2源码分析—桌面快捷图标的拖拽

Android 4.0 Launcher2源码分析—桌面快捷图标的拖拽

(2013-06-15 20:07:54)
标签:

it

 

http://blog.youkuaiyun.com/chenshaoyang0011/article/details/7854947

 

本文来自http://blog.youkuaiyun.com/chenshaoyang0011 转载请申明文章出处!

通过上一篇文章Android4.0Launcher2源码分析(五)——Workspace的滑动中,已经了解了LauncherViewTree中各层所负责的工作,在DragLayer中就负责对快捷图标和AppWidget等组件的拖拽工作。桌面的滑动和图标的拖拽是两项独立的工作,正常情况下我们用手指滑动桌面会触发滑动操作,而当长按一个图标时,则会触发图标的拖拽操作,此时再滑动则会拖拽图标移动而桌面不会滑动。那么这里就分两大部分来探讨:1、拖拽操作的启动。2、拖拽。


 

一、拖拽操作的启动

那么首先进入Launcher.onCreate()中来探究下如何激活拖拽的状态。

  1. protected void onCreate(Bundle savedInstanceState)  
  2.         ......  
  3.         setupViews();  
  4.         ......  
  5.      

接着进入setupViews();

  1. private void setupViews()  
  2.         ......  
  3.         mWorkspace.setOnLongClickListener(this);  
  4.         ......  
  5.      

从这里我们可以看到对Workspace设置了OnLongClickListener,而Launcher又实现了这个接口。接着进入Launcher.onLongClick()

  1. public boolean onLongClick(View v)  
  2.         ......  
  3.         if (!(v instanceof CellLayout))  
  4.             (View) v.getParent().getParent();  
  5.          
  6.         resetAddInfo();  
  7.         CellLayout.CellInfo longClickCellInfo (CellLayout.CellInfo) v.getTag();  
  8.         ......  
  9.         // The hotseat touch handling does not go through Workspace, and we always allow long press   
  10.         // on hotseat items.   
  11.         final View itemUnderLongClick longClickCellInfo.cell;  
  12.         boolean allowLongPress isHotseatLayout(v) || mWorkspace.allowLongPress();  
  13.         if (allowLongPress && !mDragController.isDragging())  
  14.             if (itemUnderLongClick == null)  
  15.                 ......  
  16.             else  
  17.                 if (!(itemUnderLongClick instanceof Folder))  
  18.                     // User long pressed on an item   
  19.                     mWorkspace.startDrag(longClickCellInfo);  
  20.                  
  21.              
  22.          
  23.         return true;  
  24.      

当用户在一个item上长按时,则itemUnderLongClick != null,再通过调用Workspace.startDrag()来激活item的拖拽。下面先通过时序图来看下拖拽状态激活所经历的过程:

Android <wbr>4.0 <wbr>Launcher2源码分析—桌面快捷图标的拖拽

图标拖拽功能的激活大概可以分为六步,下面就一步一步的探究下其中的实现:

Step1:Workspace.startDrag(CellLayout.CellInfocellInfo)

  1. void startDrag(CellLayout.CellInfo cellInfo)  
  2.         View child cellInfo.cell;  
  3.         ......  
  4.         mDragInfo cellInfo;  
  5.         //使图标从桌面上消失,给人一种被“拖到空中”的感觉   
  6.         child.setVisibility(GONE);  
  7.         ......  
  8.         final Canvas canvas new Canvas();  
  9.   
  10.         // We need to add extra padding to the bitmap to make room for the glow effect   
  11.         final int bitmapPadding HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;  
  12.   
  13.         // The outline is used to visualize where the item will land if dropped   
  14.         //图标的轮廓,在桌面上的对应的位置绘制图标的轮廓,显示当手松开图标时它在桌面上的落点   
  15.         mDragOutline createDragOutline(child, canvas, bitmapPadding);  
  16.         beginDragShared(child, this);  
  17.      

在这个方法中,主要的工作就是让图标从桌面上消失,并且显示一个图标的外部轮廓,以表明它将要放置的位置,其显示的效果如下

Android <wbr>4.0 <wbr>Launcher2源码分析—桌面快捷图标的拖拽

显示图标的轮廓可以从视觉上给用户更加好的体验。接着,进入beginDragShared()

Step2:Workspace.beginDragShared(Viewchild,DragSource source)

 

  1. public void beginDragShared(View child, DragSource source)  
  2.         ......  
  3.         // The drag bitmap follows the touch point around on the screen   
  4.         final Bitmap createDragBitmap(child, new Canvas(), bitmapPadding);  
  5.         final int bmpWidth b.getWidth();  
  6.   
  7.         //我们将在DragLayer中绘制“拖拽后”的图标,通过DragLayer.getLoactionInDragLayer()   
  8.         //获取在DragLayer中的坐标,并存放在mTempXY中。   
  9.         mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);  
  10.         final int dragLayerX (int) mTempXY[0] (child.getWidth() bmpWidth) 2;  
  11.         int dragLayerY mTempXY[1] bitmapPadding 2;  
  12.   
  13.         Point dragVisualizeOffset null;  
  14.         Rect dragRect null;  
  15.           
  16.         //无论child是BubbleTextView或者PagedViewIncon或者FolderIcon的实例   
  17.         //定位图标的位置与大小   
  18.         if (child instanceof BubbleTextView || child instanceof PagedViewIcon)  
  19.             int iconSize r.getDimensionPixelSize(R.dimen.app_icon_size);  
  20.             int iconPaddingTop r.getDimensionPixelSize(R.dimen.app_icon_padding_top);  
  21.             int top child.getPaddingTop();  
  22.             int left (bmpWidth iconSize) 2;  
  23.             int right left iconSize;  
  24.             int bottom top iconSize;  
  25.             dragLayerY += top;  
  26.             // Note: The drag region is used to calculate drag layer offsets, but the   
  27.             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.   
  28.             dragVisualizeOffset new Point(-bitmapPadding 2, iconPaddingTop bitmapPadding 2);  
  29.             dragRect new Rect(left, top, right, bottom);  
  30.         else if (child instanceof FolderIcon)  
  31.             int previewSize r.getDimensionPixelSize(R.dimen.folder_preview_size);  
  32.             dragRect new Rect(0, 0, child.getWidth(), previewSize);  
  33.          
  34.         ......  
  35.         mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),  
  36.                 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);  
  37.         b.recycle();  
  38.      

Workspace.beginSharedDrag()中主要所做的工作就是计算拖拽目标位于DragLayer中的坐标和尺寸大小,接着又调用DragController.startDrag()

Step3:DragController.startDrag(Bitmapb ,int dragLayerX, int dragLayerY,DragSource source, ObjectdragInfo, int dragAction, Point dragOffset, RectdragRegion)

  1.   
  2. public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,  
  3.         DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion)  
  4.     ......  
  5.     for (DragListener listener mListeners)  
  6.         listener.onDragStart(source, dragInfo, dragAction);  
  7.      
  8.   
  9.     final int registrationX mMotionDownX dragLayerX;  
  10.     final int registrationY mMotionDownY dragLayerY;  
  11.   
  12.     final int dragRegionLeft dragRegion == null dragRegion.left;  
  13.     final int dragRegionTop dragRegion == null dragRegion.top;  
  14.   
  15.     //设置mDragging=true,表示拖拽已经开始   
  16.     //在DragLayer的onInterceptTouchEvent()中根据这个值判断是否拦截MotionEvent   
  17.     mDragging true;  
  18.   
  19.     //实例化DragObject,表示拖拽的对象   
  20.     //封装了拖拽对象的信息   
  21.     mDragObject new DropTarget.DragObject();  
  22.   
  23.     mDragObject.dragComplete false;  
  24.     mDragObject.xOffset mMotionDownX (dragLayerX dragRegionLeft);  
  25.     mDragObject.yOffset mMotionDownY (dragLayerY dragRegionTop);  
  26.     mDragObject.dragSource source;  
  27.     mDragObject.dragInfo dragInfo;  
  28.     ......  
  29.     final DragView dragView mDragObject.dragView new DragView(mLauncher, b, registrationX,  
  30.             registrationY, 0, 0, b.getWidth(), b.getHeight());  
  31.     ......  
  32.     //将拖拽的图标显示在DragLayer中   
  33.     dragView.show(mMotionDownX, mMotionDownY);  
  34.     handleMoveEvent(mMotionDownX, mMotionDownY);  
  35.  


代码中显示通过一个for语句调用了DragListener.onDragStart()方法,通知它们已经开始拖拽了,其中由于Workspace实现了DragListener并且添加到了mListeners中。所以Workspace.onDragStart()被调用。然后又封装了一个DragObject对象,封装DragSourceDragInfoDragView等信息。接着,将调用DragView.show()DragView显示在DragLayer中。

Step4:DragView.show(int touchX,inttouchY)

 

  1.   
  2.     public void show(int touchX, int touchY)  
  3.         //将DragView添加到DragLayer中   
  4.         mDragLayer.addView(this);  
  5.           
  6.         //设置位置、尺寸等信息   
  7.         DragLayer.LayoutParams lp new DragLayer.LayoutParams(0, 0);  
  8.         lp.width mBitmap.getWidth();  
  9.         lp.height mBitmap.getHeight();  
  10.         lp.x touchX mRegistrationX;  
  11.         lp.y touchY mRegistrationY;  
  12.         lp.customPosition true;  
  13.         setLayoutParams(lp);  
  14.         mLayoutParams lp;  
  15.         mAnim.start();  
  16.      

其中的内容很简单易懂,就是在将DragView添加到了DragLayer中,并且在合适的位置显示了出来。接着应该调用在DragController.startDrag()中调用handleMoveEvent(),这个将在后文将拖拽过程分析时在看。到这一步,拖拽操作的启动过程就完成了。接着就可以拖拽图标了。

 

二、拖拽

通过了前面文章的分析,已经知道了拖拽过程的实现在DragLayer中,当进行图标的拖拽时,DragLayer.onInterceptTouchEvent()就会对MotionEvent进行拦截。并且
在自身的onTouchEvent()方法中进行操作,从而实现图标的移动。由于onInterceptTouchEvent()拦截了MotionEvent,因此Workspace等UI控件不会接收到事件,从而不会产生
干扰。那么首先进入DragLayer.onInterceptTouchEvent():

  1. public boolean onInterceptTouchEvent(MotionEvent ev)  
  2.         ......  
  3.         return mDragController.onInterceptTouchEvent(ev);  
  4.      

代码中省略了与其他功能的不部分代码,最后调用了DragController.onInterceptTouchEvent() ,并取其返回值作为自身方法的返回值。进入DragController.onInterceptTouchEvent()。

  1.   
  2.     public boolean onInterceptTouchEvent(MotionEvent ev)  
  3.         ......  
  4.         final int action ev.getAction();  
  5.   
  6.         final int[] dragLayerPos getClampedDragLayerPos(ev.getX(), ev.getY());  
  7.         final int dragLayerX dragLayerPos[0];  
  8.         final int dragLayerY dragLayerPos[1];  
  9.   
  10.         switch (action)  
  11.             case MotionEvent.ACTION_MOVE:  
  12.                 break;  
  13.             case MotionEvent.ACTION_DOWN:  
  14.                 // Remember location of down touch   
  15.                 mMotionDownX dragLayerX;  
  16.                 mMotionDownY dragLayerY;  
  17.                 mLastDropTarget null;  
  18.                 break;  
  19.             case MotionEvent.ACTION_UP:  
  20.                 if (mDragging)  
  21.                     drop(dragLayerX, dragLayerY);  
  22.                  
  23.                 endDrag();  
  24.                 break;  
  25.             case MotionEvent.ACTION_CANCEL:  
  26.                 cancelDrag();  
  27.                 break;  
  28.          
  29.   
  30.         return mDragging;  
  31.      

这里我们关心的是它的返回值。可以看到方法将mDragging作为返回值。当触发了拖拽状态,在的DragController.startDrag()中将mDragging的值改为true。所以这里也将返回trueDragLayer将拦截MotionEvent,并传给自身的onTouchEvent()方法,在onTouchEvent()中对图标进行移动,刷新界面。


 

  1.   
  2. public boolean onTouchEvent(MotionEvent ev)  
  3.     ......  
  4.     final int action ev.getAction();  
  5.     final int[] dragLayerPos getClampedDragLayerPos(ev.getX(), ev.getY());  
  6.     final int dragLayerX dragLayerPos[0];  
  7.     final int dragLayerY dragLayerPos[1];  
  8.   
  9.     switch (action)  
  10.     case MotionEvent.ACTION_DOWN:  
  11.         // Remember where the motion event started   
  12.         mMotionDownX dragLayerX;  
  13.         mMotionDownY dragLayerY;  
  14.   
  15.         //判断当前的触点是否处于屏幕边缘的ScrollZone,当处于这个区域时   
  16.         //状态mScrollState将转变为SCROLL,并且在一定时间的停留之后,屏幕滑动到另一屏。   
  17.         if ((dragLayerX mScrollZone) || (dragLayerX mScrollView.getWidth() mScrollZone))  
  18.             mScrollState SCROLL_WAITING_IN_ZONE;  
  19.             mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);  
  20.         else  
  21.             mScrollState SCROLL_OUTSIDE_ZONE;  
  22.          
  23.         break;  
  24.     case MotionEvent.ACTION_MOVE:  
  25.         //调用handleMoveEvent()处理图标移动   
  26.         handleMoveEvent(dragLayerX, dragLayerY);  
  27.         break;  
  28.     case MotionEvent.ACTION_UP:  
  29.         // Ensure that we've processed move event at the current pointer location.   
  30.         handleMoveEvent(dragLayerX, dragLayerY);  
  31.   
  32.         mHandler.removeCallbacks(mScrollRunnable);  
  33.         if (mDragging)  
  34.           "WHITE-SPACE: pre">   //根据目前相对DragLayer的坐标,将图标“降落”到指定的DropTarget上。   
  35.             drop(dragLayerX, dragLayerY);  
  36.          
  37.         endDrag();  
  38.         break;  
  39.     case MotionEvent.ACTION_CANCEL:  
  40.         cancelDrag();  
  41.         break;  
  42.      
  43.     return true;  
  44.  

onTouchEvent()中处理的事件涉及到不同状态之间的转换,以及每种状态之下对相应的MotionEvent的对策。这里同样,从简单的情况入手:图标拖拽起来后,移动一段距离,在屏幕的另一个位置放下。

首先,当拖拽起图标时,拖拽图标的状态被启动,这就是第一部分所探讨的内容。

然后,移动拖拽的图标。此时触发了MotionEvent.ACTION_MOVE事件,紧接着调用handleMoveEvent()来处理移动。进入handleMoveEvent()来看看图标移动是怎么实现的。

  1. private void handleMoveEvent(int x, int y)  
  2.         //更新在DragLayer中的位置   
  3.         mDragObject.dragView.move(x, y);  
  4.   
  5.         // Drop on someone?   
  6.         final int[] coordinates mCoordinatesTemp;  
  7.           
  8.         //根据当前的位置寻找DropTarget对象来放置图标   
  9.         DropTarget dropTarget findDropTarget(x, y, coordinates);  
  10.         mDragObject.x coordinates[0];  
  11.         mDragObject.y coordinates[1];  
  12.         if (dropTarget != null)  
  13.             DropTarget delegate dropTarget.getDropTargetDelegate(mDragObject);  
  14.             if (delegate != null)  
  15.                 dropTarget delegate;  
  16.              
  17.   
  18.             if (mLastDropTarget != dropTarget)  
  19.                 if (mLastDropTarget != null)  
  20.                     //从最后一次记录的DropTarget中退出   
  21.                     mLastDropTarget.onDragExit(mDragObject);  
  22.                  
  23.                 //进入到当前寻找到的DropTarget   
  24.                 dropTarget.onDragEnter(mDragObject);  
  25.              
  26.             dropTarget.onDragOver(mDragObject);  
  27.         else  
  28.             if (mLastDropTarget != null)  
  29.                 mLastDropTarget.onDragExit(mDragObject);  
  30.              
  31.          
  32.         mLastDropTarget dropTarget;  
  33.   
  34.         // Scroll, maybe, but not if we're in the delete region.   
  35.         boolean inDeleteRegion false;  
  36.         if (mDeleteRegion != null)  
  37.             inDeleteRegion mDeleteRegion.contains(x, y);  
  38.          
  39.   
  40.         // After scroll, the touch point will still be in the scroll region.   
  41.         // Rather than scrolling immediately, require bit of twiddling to scroll again   
  42.         final int slop ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();  
  43.         mDistanceSinceScroll +=  
  44.             Math.sqrt(Math.pow(mLastTouch[0] x, 2) Math.pow(mLastTouch[1] y, 2));  
  45.         mLastTouch[0] x;  
  46.         mLastTouch[1] y;  
  47.   
  48.         //判断当前拖拽的图标是否处于ScrollZone即滑动区域。   
  49.         //并且根据在哪个一个ScrollZone来处理屏幕滑动的方向。   
  50.         if (!inDeleteRegion && mScrollZone)  
  51.             if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll slop)  
  52.                 mScrollState SCROLL_WAITING_IN_ZONE;  
  53.                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT))  
  54.                     mScrollRunnable.setDirection(SCROLL_LEFT);  
  55.                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);  
  56.                  
  57.              
  58.         else if (!inDeleteRegion && mScrollView.getWidth() mScrollZone)  
  59.             if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll slop)  
  60.                 mScrollState SCROLL_WAITING_IN_ZONE;  
  61.                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT))  
  62.                     mScrollRunnable.setDirection(SCROLL_RIGHT);  
  63.                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);  
  64.                  
  65.              
  66.         else  
  67.             if (mScrollState == SCROLL_WAITING_IN_ZONE)  
  68.                 mScrollState SCROLL_OUTSIDE_ZONE;  
  69.                 mScrollRunnable.setDirection(SCROLL_RIGHT);  
  70.                 mHandler.removeCallbacks(mScrollRunnable);  
  71.                 mDragScroller.onExitScrollArea();  
  72.              
  73.          
  74.      

handleMoveEvent()主要处理拖拽过程中需要处理的事务。包括:1、在更新图标在屏幕中的位置,并刷新UI2、判断图标当前所处的位置。包括SCROLL_OUTSIDE_ZONESCROLL_WAITING_IN_ZONE,对处于SCROLL_WAITING_IN_ZONE位置时,需要根据具体的位置,向前或向后切换显示的屏幕。再回到上面假设的情况中。则此时只是简单的刷新了位置信息,并重新绘制图标。

最后,当松开拖拽的对象时,触发了MotionEvent.ACTION_UP事件。则进入下面一段代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值