`
fuerbosi
  • 浏览: 461184 次
文章分类
社区版块
存档分类
最新评论

Launcher 拖拽 流程小结『android 2.3 2.2』

 
阅读更多

问:尼玛Android 4.1Jelly Bean都发布了,你还bb2.3,坑爹呢,这是?

答:这个真不好意思了,屌丝的特点就是后知后觉。

问:那有何用?

答:可以很不负责任的说,从2.2以后launcher 拖拽流程基本没变化。

问:基本?那还是有变化,到底还是坑爹。

答:“好吧,你赢了”。


-------------------------------------------------------------------------------------------------------------我是风格线--------

回归正题,要是做launcher的话,那拖拽事件处理、响应是必须得撸清楚的:

先来一张Launcher拖拽的时序图,以便对整个流程有个大概的把握(看不明白也没关系,跳过看下面先,回头再看就so easy了):


下面分步骤详细说明:

一,事件产生

拖拽事件的产生基于用户长按操作,分两种情况:

  • 长按桌面(workspace视图)上的子视图(应用图标,文件夹,widget)事件响应是launcher.java

 public boolean onLongClick(View v)
    {
      ...........
            else 
            {
                if (!(cellInfo.cell instanceof Folder)) 
                {
                    // 用户长按某一组件时调用
                    mWorkspace.startDrag(cellInfo);
                }
            }
        }
        return true;
    }

F2跳转之:

   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.
        // Note that Search takes focus when clicked rather than entering touch mode
      ..........
        mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
        invalidate();
        clearVacantCache();
    }

  • 长按应用主菜单(AllAppGridView视图)上的子视图(应用图标)事件响应是AllAppGridView.java

public boolean onItemLongClick(AdapterView<?> parent, View view,
			int position, long id) 
	{
		if (!view.isInTouchMode()) 
		{
			return false;
		}
		ApplicationInfo app = (ApplicationInfo) parent
				.getItemAtPosition(position);
		app = new ApplicationInfo(app);
               //用户长按应用主菜单应用图标时调用
		mDragger.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);	
	}

该步骤的事件产生最终都定位mDragger.startDrag方法,顺着它往下摸就行了。

二,mDragger为何物?

mDragger是DragController实例,我们F3可以看到DragController其实就是一个接口,里面有几个很眼熟的抽象方法

F4一下可以知道该接口的唯一子类是DragLayer.java,必须进去一探究竟,尼玛这玩意竟然继承了frameLayout,脑子里有东西闪过,soga,原来是这样:

<com.android.launcher2.DragLayer
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"

    android:id="@+id/drag_layer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Keep these behind the workspace so that they are not visible when
         we go into AllApps -->
    <include
...........

上面的代码就是launcher的主布局文件。DragLayer作为整个launcher的RootView负责所有拖拽事件的统一分发处理。因为launcher上能看到的组件都布局在它之上

为啥它是frameLayout,我就不罗嗦了。

当用户长按某一launcher组件时,DragLayer.startDrag会扯开嗓子通知那些相关组件做好接受拖拽事件的准备,并顺便悄悄的隐藏被拖拽图标的原始view。

三,既然已经跟到frameLayout视图,那么接下来应该是顺着触屏时序处理来看比较直观。

 public boolean onInterceptTouchEvent(MotionEvent ev) 
    {
      ..........
            case MotionEvent.ACTION_UP:
                if (mShouldDrop && drop(x, y)) 
                {
                    mShouldDrop = false;
                }
                endDrag();
                break;
        }

        return mDragging;
    }
onInterceptTouchEvent作为触屏事件的节奏“打断者”在这里并没啥作为,只是在up时调用endDrag,用来在合适的时候显示刚才隐藏的view。

老大不管事,那自然小弟抗大梁了。看看onTouchEvent是如何有条不紊的处理这些拖拽事件的。

 public boolean onTouchEvent(MotionEvent ev) 
    {
         .........
        switch (action) 
        {
        case MotionEvent.ACTION_DOWN:

            // 初始化mScrollState状态。
                mScrollState = SCROLL_OUTSIDE_ZONE;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            // 不断更新当前icon的位置。
            rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
            final int[] coordinates = mDropCoordinates;
            //调用findDropTarget来寻找落脚点。比如是deleteZone区域,就触发相关的事件响应。
            DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
           ........
                    dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],        
                    mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
                    dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
                    mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
           ........
            if (!inDragRegion && x < SCROLL_ZONE) 
            {
                if (mScrollState == SCROLL_OUTSIDE_ZONE)
                {
                    mScrollState = SCROLL_WAITING_IN_ZONE;
                    mScrollRunnable.setDirection(SCROLL_LEFT);
            //拖着图标往左边换屏幕。
                    postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            } 
            else if (!inDragRegion && x > getWidth() - SCROLL_ZONE)
            {
                if (mScrollState == SCROLL_OUTSIDE_ZONE) 
                {
                    mScrollState = SCROLL_WAITING_IN_ZONE;
                    mScrollRunnable.setDirection(SCROLL_RIGHT);
           //拖着图标往右边换屏幕。
	            postDelayed(mScrollRunnable, SCROLL_DELAY); 
               }
           }
           .......
        case MotionEvent.ACTION_UP:
            removeCallbacks(mScrollRunnable);
            if (mShouldDrop)
             {
          //秋后算总账了,目标view是否接受该source view,还需drop(x,y)作最后定夺。
                drop(x, y);
                mShouldDrop = false;
            }
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            endDrag();
        }
        return true;
    }

Action_move事件里要特别注意一下findDropTarget方法的实现,该事件会不断调用dropTarget的那几个抽象方法,只要是实现了该接口的view(userFolder,DeleteZone,workspace)都会根据特定的条件调用相应的重载方法,从而作出像文件夹开合,deleteZone变色等响应。

Action_up事件中咱们很熟练的F3,进了drop方法(太重要了,它决定了该拖拽事件最终的结果,是目标view接受了它还是拒绝都在这里都到裁决)。但代码其实很简短:

 private boolean drop(float x, float y)
    {
        invalidate();
        final int[] coordinates = mDropCoordinates;
        //如果上面findDropTarget是寻找落脚点,这里就是准备托付终身了。
        DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
        if (dropTarget != null) 
        {
        	if(shouldAccept == false && dropTarget.toString() == "ActionButton")
        	{
        		return false;
        	}
            dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
            //如果这里的acceptDrop返回ture,那就意味着得到了男方父母的首肯,终于找到依附的view了。
            if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo)) {
                dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
                mDragSource.onDropCompleted((View) dropTarget, true);
                return true;
            } 
            else 
            {
                mDragSource.onDropCompleted((View) dropTarget, false);
                return true;
            }
        }       
        return false;
    }


其实去年已经看这块了,怎奈人搓且奇懒无比,今天总算做个了结。










分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics