Android4.4-Launcher源碼分析系列之關鍵的類和接口之DragLayer

一、DragLayer佈局

上一篇文章分析過Launcher的佈局,它是最外層的佈局

<!-- Full screen view projects under the status bar and contains the background -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
    android:id="@+id/launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/workspace_bg" >

    <com.android.launcher3.DragLayer
        android:id="@+id/drag_layer"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- The workspace contains 5 screens of cells -->
它繼承自FrameLayout,它對應的類是DragLayer.

二、DragLayer代碼分析

官方解釋爲:A ViewGroup that coordinates dragging across its descendants.意思是說它是一個協調處理它的子view拖動的容器.那麼很明顯,我們需要關注它對屏幕觸摸事件的處理.它有三個方法onTouchEvent,onInterceptTouchEvent和onInterceptHoverEvent.在分析這三個方法之前,先看下DragLayer的構造函數,它重寫了FrameLayout的一些屬性.

 public DragLayer(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 禁止多點觸控
        setMotionEventSplittingEnabled(false);
        // 按指定順序渲染子控件
        setChildrenDrawingOrderEnabled(true);
        // 偵聽子view的add和remove事件
        setOnHierarchyChangeListener(this);
        //拖動圖標到屏幕左邊緣的圖片
        mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
        //拖動圖標到屏幕右邊緣的圖片
        mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
    }

我已經在代碼寫上了註釋,mLeftHoverDrawable是拖動桌面上的圖標到屏幕左側邊緣時顯示的圖片,默認的圖片實現的是高亮的效果,這裏我把它替換爲手指的圖片,便於觀看效果,如下圖

不瞭解android屏幕事件傳遞機制的可以先去了解下.接下來講上面提到的三個方法.

當點擊屏幕時,會先進入onInterceptTouchEvent方法

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

          if (action == MotionEvent.ACTION_DOWN) {
            if (handleTouchDown(ev, true)) {
            	 
            	return true;
            }
        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            if (mTouchCompleteListener != null) {
                mTouchCompleteListener.onTouchComplete();
            }
            mTouchCompleteListener = null;
        }
        clearAllResizeFrames();
        return mDragController.onInterceptTouchEvent(ev);
    }
先判斷action是不是down,點擊事件必然會執行down,那麼繼續判斷handleTouchDown(ev, true)是否爲true,爲true則攔截,這個方法是當正在縮放插件、打開文件夾、編輯文件夾名稱的時候返回true.

 private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
        Rect hitRect = new Rect();
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        for (AppWidgetResizeFrame child: mResizeFrames) {
        	// 獲得widget縮放框佔據的矩形區域的矩形座標
        	child.getHitRect(hitRect);
        	// 動作如果碰到了子控件。檢查如果座標處於縮放框可以對widget進行縮放操作的區域中,則禁止父控件再響應觸控事件
        	if (hitRect.contains(x, y)) {
                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
                    mCurrentResizeFrame = child;
                    mXDown = x;
                    mYDown = y;
                    requestDisallowInterceptTouchEvent(true);
                    return true;
                }
            }
        }
        // 獲取當前打開的文件夾。
         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
         if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
        	 // 如果點擊的是文件夾內區域並且正處於重命名狀態,則取消重命名
        	if (currentFolder.isEditingName()) {
                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
                    currentFolder.dismissEditingName();
                    return true;
                }
            }
        	getDescendantRectRelativeToSelf(currentFolder, hitRect);
            // 如果文件夾正在打開狀態。那麼如果點擊的是文件夾以外區域則關閉文件夾。
            if (!isEventOverFolder(currentFolder, ev)) {
                mLauncher.closeFolder();
                return true;
            }
        }
        return false;
    }
判斷觸控事件是否經過了文件夾圖標的文字區域的方法

private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
        	
        	return true;
        }
        return false;
    }
判斷觸控事件是否經過了文件夾圖標區域的方法

private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
        getDescendantRectRelativeToSelf(folder, mHitRect);
        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
            
        	return true;
        }
        return false;
    }

你們可以在裏面加上打印或者Toast驗證下,想出現文件夾,就把一個圖標移動到另一個圖標上面,自動出現文件夾.

在down下之後,如果handleTouchDown(ev, true)返回爲false,則進入到clearAllResizeFrames方法,這個方法是清除widget的所有縮放框,

widget不是可以調整大小嗎,當點擊下去的時候就重置widget大小,該方法如下

         /**
	 * 清除widget的所有縮放框
	 */
    public void clearAllResizeFrames() {
        if (mResizeFrames.size() > 0) {
            for (AppWidgetResizeFrame frame: mResizeFrames) {
                frame.commitResize();
                removeView(frame);
            }
            mResizeFrames.clear();
        }
    }
執行完這個方法後會返回DragController的onInterceptTouchEvent值

return mDragController.onInterceptTouchEvent(ev);
如果你點擊查看會發現,如果正在拖動圖標則返回true,否則返回false.
當你點擊下圖標就鬆開,那麼只執行onInterceptTouchEvent方法.當你點擊圖標之後拖動,那麼接下來會執行onTouchEvent方法

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        int action = ev.getAction();
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        if (action == MotionEvent.ACTION_DOWN) {
            if (handleTouchDown(ev, false)) {
            	return true;
            }
          } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        	System.out.println("307");
        	if (mTouchCompleteListener != null) {
                mTouchCompleteListener.onTouchComplete();
            }
            mTouchCompleteListener = null;
            
        }

        if (mCurrentResizeFrame != null) {
            handled = true;
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
                     break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
                    mCurrentResizeFrame.onTouchUp();
                    mCurrentResizeFrame = null;
                   
            }
        }
        // 如果沒有正在調節widget大小也沒有打開文件夾,則進入正題,開始拖動
        if (handled) return true;
        return mDragController.onTouchEvent(ev);
    }
它會調用DragController的onTouchEvent方法.交給子view處理.

最後是這個onInterceptHoverEvent方法,意思是如果沒有正在縮放插件、沒有打開文件夾、沒有編輯文件夾名稱。 DragLayer將處理觸控動作,

並取消一切widget的Resize動作(如果有)

    @Override
    public boolean onInterceptHoverEvent(MotionEvent ev) {
        if (mLauncher == null || mLauncher.getWorkspace() == null) {
            return false;
        }
        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
        if (currentFolder == null) {
            return false;
        } else {
                AccessibilityManager accessibilityManager = (AccessibilityManager)
                        getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
            if (accessibilityManager.isTouchExplorationEnabled()) {
                final int action = ev.getAction();
                boolean isOverFolder;
                switch (action) {
                    case MotionEvent.ACTION_HOVER_ENTER:
                        isOverFolder = isEventOverFolder(currentFolder, ev);
                        if (!isOverFolder) {
                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                            mHoverPointClosesFolder = true;
                            return true;
                        } else if (isOverFolder) {
                            mHoverPointClosesFolder = false;
                        } else {
                            return true;
                        }
                    case MotionEvent.ACTION_HOVER_MOVE:
                        isOverFolder = isEventOverFolder(currentFolder, ev);
                        if (!isOverFolder && !mHoverPointClosesFolder) {
                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                            mHoverPointClosesFolder = true;
                            return true;
                        } else if (isOverFolder) {
                            mHoverPointClosesFolder = false;
                        } else {
                            return true;
                        }
                }
            }
        }
        return false;
    }
好了,關於DragLayer的介紹就這麼多了,關於其它的一些細節,我會在後面上傳註釋後的Launcher源碼的.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章