Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Launcher3 拖動圖標筆記

Launcher3 拖動圖標筆記

編輯:關於Android編程

本文主要記錄了Launcher3拖動時的流程和代碼記錄,在桌面圖標拖動時會引起圖標的重排,拖動時受影響的圖標在文中由item或cell來表示。

圖標點擊效果和搖動效果

mTouchFeedbackView為點擊按下時圖標表面一層透明灰色動畫效果,這個動畫效果在Celllayout中,在構造函數中addView加入,BubbleTextView 中 setStayPressed方法調用這個動畫效果,在點擊圖標時呈現,下圖就是我把Wallpaper改變成純白色,更改動畫位置,這樣效果很明顯。

點擊圖標動畫

addView(mTouchFeedbackView, (int) (grid.cellWidthPx * 1.5), (int) (grid.cellHeightPx * 1.5));
addView(mShortcutsAndWidgets);

拖動時受影響的Item有一個搖動的(shake)動畫,這個動畫效果是通過ReorderPreviewAnimation和mShakeAnimators實現,受影響的item由ItemConfiguration管理,Celllayout-completeAndClearReorderPreviewAnimations方法播放動畫,在適當時機調用。這個動畫比較有意思,單純的線性動畫沒有這種效果,看了代碼才發現用到了貝塞爾曲線線性方程,這部分代碼主要在Celllayout中。

圖標的占位

Celllayout的boolean[][] mOccupied存儲桌面圖標布局中是否有圖標占位,存在則為true,該對象在CellLayout的addViewToCellLayout方法賦值,這個方法在Workspace-addInScreen方法中調用,而桌面初始化時的bind過程會一直調用到這個方法。

圖標拖動處理

DragController和DragLayer

DragLayer是Launcher3中處理拖動圖標事件時自定義View,DragController的注釋為:發起一個在一個View內或在幾個View之間拖動事件的類。從名字可以看出這個類是一個調節者或控制者。

在DragLayer-onInterceptTouchEvent方法將事件給DragController-onInterceptTouchEvent方法處理。拖動圖標的事件開始處理時,會一直調用到DragController-startDrag方法,此時,startDrag裡將mDragging置為true,而DragController-onInterceptTouchEvent返回mDragging,此時截斷事件傳遞,當前View處理觸摸事件。類似的,DragLayer的onTouchEent方法也將事件給DragController的onTouchEent方法處理。

handleMoveEvent方法為處理觸摸事件的主要方法,在handleMoveEvent方法中調用findDropTarget取的當前的具體的DropTarget對象,DragController中有一個mDropTargets鏈表,這個鏈表中在桌面的加載和操作會包含進來具體DropTarget對象,在Launcher3中,實現DropTarget類的有Folder和Workspace等,這個interface抽象了拖動時落點對象。DropTarget-isDropEnabled方法,表示當前這個落點View是否可以Drop,當該方法返回false時,顯然是不能將拖動的Object放到這裡來的。checkTouchMove方法檢查拖動時的狀態,根據拖動時DropTarget之間的關系調用DropTarget聲明的onDrop,onDragEnter,onDragExit,onDragOver方法進行視圖的演示,最後調用checkScrollState方法,這個方法主要是對拖動時的翻頁進行判斷處理。

DropTarget為Workspace的拖動處理

根據checkTouchMove方法,在Workspace中開始拖動圖標時,圖標開始時在Workspace中,此時DropTarget也為Workspace,所以剛開始時調用Workspace的onDragEnter方法,緊接著調用Workspace的onDragOver方法。而onDragExit方法的調用時機為拖動的Cell離開當前Target,當前Target變為mLastDropTarget,又沒有進入一個有效的Target時。
Workspace的onDragEnter方法,這個方法比較簡單,對Workspace的拖動時的涉及到的變量進行了初始化。

Workspace的onDragOver方法,在Workspace中有一個mDragViewVisualCenter變量,根據字面理解,這個變量是拖動對象的視覺中心位置,由getDragViewVisualCenter方法計算而來,在對拖動處理時多處使用,此外拖動的時候也要判斷當前位置Layout的是處於翻動的頁面還是Hotseat中,確定mDragTargetLayout。接著findNearestArea方法根據mDragViewVisualCenter先大致當前的落點,findNearestArea方法會調用到Celllayout的findNearestArea方法,根據不同情況在調用的過程中會分別考慮和不考慮當前頁面的其他item的占用情況。確定了可能的落點後首先對Folder情況進行處理,分別為新建和加入已有的Folder兩種,然後是落點已經被其他的Item占據的情況,這裡調用performReorder方法處理,這個方法相當長,拖動過程中和結束拖動放下時的onDrag方法中都會調用這個方法,拖動過程中圖標的重排效果就是在此方法中實現的。Workspace的onDragExit方法,根據上面onDragExit的調用時機,這裡主要確定拖動圖標離開有效DragTarget時最後的落點。

根據DragController的onTouchEent方法,觸摸事件為MotionEvent.ACTION_UP時,拖動事件結束,這時有兩種可能:當滿足快速向上扔時,刪除拖動的item,isFlingingToDelete方法判斷條件是否滿足。如果不滿足扔的條件,調用drop方法,drop方法調用DropTarget的acceptDrop方法,判斷該Drop事件是否發生,依此調用DropTarget的onDrop方法。

Workspace-onDrop()流程,方法參數為DropTarget-DragObject對象,首先計算拖動View的視覺中心mDragViewVisualCenter,dropTargetLayout為Drop的Celllayout對象,下一步判斷當前是否在Hotseat上,求出相對於dropTargetLayout的視覺中心坐標。如果DragObject-dragSource!=Worspace,轉而調用onDropExternal(),否則繼續處理onDrop()的內容。接著調用findNearestArea方法球drop的xy的值,Workspace-findNearestArea()調用到CellLayout-findNearestArea(),該方法是一個重要的方法,作用是找到離落點最近距離cell,其中有一個boolean參數ignoreOccupied,當ignoreOccupied為false時,尋找cell時不考慮已經占據的區域,為true時考慮。

接下來開始處理落點和文件夾的關系,如果落點處可以合成一個Folder,調用Workspace-createUserFolderIfNecessary()方法,如果拖動的圖標可以加進一個文件夾,則調用Workspace-addToExistingFolderIfNecessary()方法。如果不滿足文件夾的條件,則調用CellLayout-performReorder方法,這個方法就是處理拖動圖標時,如果當前落點被占據時,擠開當前圖標的效果。

AppWidget可能在拖動時發生縮小,因此會調用AppWidgetResizeFrame-updateWidgetSizeRanges方法,拖動時可能落點在別的頁面,所以還會有頁面滑動的效果。如果滿足則更新位置,保存新的位置信息到數據庫中,播放動畫效果,否則彈回原來位置。

acceptDrop方法的調用過程和邏輯和onDrop方法差不多,需要注意的是調用performReorder方法時mode參數不同。

除了DropTarget,WorkSpace還implements了DragController.DragListener,DragCtroller中存在一個mListeners鏈表,在桌面加載時會將相應的對象加進來,比如Workspace在Launcher的setupViews方法中加入。這個接口聲明了onDragStart方法和onDragEnd方法,上面說過開始拖動時調用DragController的startDrag方法,startDrag方法中依次調用mListeners中對象的onDragStart方法,這裡處理的是開始拖動時桌面受影響的View對象,比如剛開始拖動時Workspace有一些提示效果,搜索刪除按鈕(SearchDropTargetBar)會由搜索界面變為刪除等,這些View對象都是繼承了這個借口的。除此外,startDrag方法還顯示了拖動item的DragView。Workspace的onDragStart方法處理了一些界面相關的操作,比如鎖定屏幕方向,生成額外的空白頁等。onDragEnd方法即為相反的操作。

Workspace還實現了一個拖動相關的接口為DragSource接口,這個接口定義了拖動可以從它本身開始的對象,就是可以從Workspace開始拖動一個圖標,DragSorce聲明了一系列拖動時判斷條件或某種狀態回調的方法。

具體方法

findNearestArea方法,這個在拖動過程出鏡頻率相當高。

    /**
     * Find a vacant area that will fit the given bounds nearest the requested
     * cell location. Uses Euclidean distance to score multiple vacant areas.
     */
    int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
            View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
            boolean[][] occupied) {
        //初始化一個mCountX*mCountY的棧
        lazyInitTempRectStack();
        // mark space take by ignoreView as available (method checks if ignoreView is null)
        //此時occupied為mOccupied,這裡將ignoreView的位置置為false,即忽略當前位置View
        markCellsAsUnoccupiedForView(ignoreView, occupied);
        // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
        // to the center of the item, but we are searching based on the top-left cell, so
        // we translate the point over to correspond to the top-left.
        // 即左上角cell的中心pixelX -= (mCellWidth*span + mWidthGap*(spanX-1))/2-mCellWidth/2
        pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
        pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;

        // Keep track of best-scoring drop area
        final int[] bestXY = result != null ? result : new int[2];
        double bestDistance = Double.MAX_VALUE;
        final Rect bestRect = new Rect(-1, -1, -1, -1);
        final Stack validRegions = new Stack();
        final int countX = mCountX;
        final int countY = mCountY;

        if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
                spanX < minSpanX || spanY < minSpanY) {
            return bestXY;
        }

        for (int y = 0; y < countY - (minSpanY - 1); y++) {
            inner:
            for (int x = 0; x < countX - (minSpanX - 1); x++) {
                int ySize = -1;
                int xSize = -1;

                //為true時代表要考慮已經被占據的位置
                //先尋找符合minSpanX和minSpanY的位置
                //然後向分x,y方向向外擴大,試探當前位置最大的可能性
                //findConfigurationNoShuffle方法調用時為true
                if (ignoreOccupied) {
                    // First, let's see if this thing fits anywhere
                    for (int i = 0; i < minSpanX; i++) {
                        for (int j = 0; j < minSpanY; j++) {
                            if (occupied[x + i][y + j]) {
                                continue inner;
                            }
                        }
                    }
                    xSize = minSpanX;
                    ySize = minSpanY;

                    // We know that the item will fit at _some_ acceptable size, now let's see
                    // how big we can make it. We'll alternate between incrementing x and y spans
                    // until we hit a limit.
                    boolean incX = true;
                    //為true停止
                    boolean hitMaxX = xSize >= spanX;
                    boolean hitMaxY = ySize >= spanY;
                    while (!(hitMaxX && hitMaxY)) {
                        if (incX && !hitMaxX) {
                            for (int j = 0; j < ySize; j++) {
                                if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
                                    // We can't move out horizontally
                                    hitMaxX = true;
                                }
                            }
                                                // We know that the item will fit at _some_ acceptable size, now let's see
                    // how big we can make it. We'll alternate between incrementing x and y spans
                    // until we hit a limit.
                    boolean incX = true;
                    //為true停止
                    boolean hitMaxX = xSize >= spanX;
                    boolean hitMaxY = ySize >= spanY;
                    while (!(hitMaxX && hitMaxY)) {
                        if (incX && !hitMaxX) {
                            for (int j = 0; j < ySize; j++) {
                                if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
                                    // We can't move out horizontally
                                    hitMaxX = true;
                                }
                            }
                            if (!hitMaxX) {
                                xSize++;
                            }
                        } else if (!hitMaxY) {
                            for (int i = 0; i < xSize; i++) {
                                if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
                                    // We can't move out vertically
                                    hitMaxY = true;
                                }
                            }
                            if (!hitMaxY) {
                                ySize++;
                            }
                        }
                        //為true或大於等於span
                        hitMaxX |= xSize >= spanX;
                        hitMaxY |= ySize >= spanY;
                        incX = !incX;
                    }
                    incX = true;
                    hitMaxX = xSize >= spanX;
                    hitMaxY = ySize >= spanY;
                }
                //求(x, y)的點的坐標
                final int[] cellXY = mTmpXY;
                cellToCenterPoint(x, y, cellXY);

                // We verify that the current rect is not a sub-rect of any of our previous
                // candidates. In this case, the current rect is disqualified in favour of the
                // containing rect.
                Rect currentRect = mTempRectStack.pop();
                currentRect.set(x, y, x + xSize, y + ySize);
                boolean contained = false;
                for (Rect r : validRegions) {
                    if (r.contains(currentRect)) {
                        contained = true;
                        break;
                    }
                }
                validRegions.push(currentRect);
                double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
                        + Math.pow(cellXY[1] - pixelY, 2));
                if ((distance <= bestDistance && !contained) ||
                        currentRect.contains(bestRect)) {
                    bestDistance = distance;
                    bestXY[0] = x;
                    bestXY[1] = y;
                    if (resultSpan != null) {
                        resultSpan[0] = xSize;
                        resultSpan[1] = ySize;
                    }
                    bestRect.set(currentRect);
                }
            }
        }
        // re-mark space taken by ignoreView as occupied
        // 恢復occupied的值
        markCellsAsOccupiedForView(ignoreView, occupied);

        // Return -1, -1 if no suitable location found
        if (bestDistance == Double.MAX_VALUE) {
            bestXY[0] = -1;
            bestXY[1] = -1;
        }
        recycleTempRects(validRegions);
        return bestXY;
    }

在後面拖動時具體執行桌面重排時會調用到findNearestArea方法的重載方法,還會權衡建議的方向向量。

rearrangementExists方法:
參數solution為ItemConfiguration對象,ItemConfiguration保存了當前桌面各個item的情況。首先確定拖動View的Rect,使用Rect.intersects方法,得出soulution的intersectingViews列表,這裡是拖動時影響到的item。接下來實現具體的方案,根據注釋,首先找出符合推力的方案attemptPushInDirection方法,拖動時,一個被推出當前位置item也會擠開別的item,如果失敗,addViewsToTempLocation方法,嘗試受影響的iew整塊移動,如果還是不想,遍歷mIntersectingViews,addViewToTempLocation方法分別嘗試放置到新位置。

attemptPushInDirection方法:
若xy方向上均有作用力,先pushViewsToTempLocation()移動x軸,再移動y軸,若正方向上移動失敗,換方向,若只有一個方向有作用力,以以下順序推動,正方向,反方向,xy軸互換,xy軸互換反方向。確定方向後,調用pushViewsToTempLocation方法。

pushViewsToTempLocation方法:
這個方法裡初始化了ViewCluster對象,這個類有四個數組(leftEdge,rightEdge,topEdge和bottomEdge)記錄了上下左右邊上當前影響的Views的邊界位置,用來精確的表示一串view的邊界,以便於在拖動時操作View的情況。

ViewCluster
例如上圖4*4的WZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmtzcGFjZaOsyOe5+83Ptq/Su7j2aXRlbbTT09K/v738yrHW03dpZGdldMqxo6zKsdbTd2lkZ2V0vs3Oqsrc07DP7LXEVmlld6OstMvKsVZpZXdDbHVzdGVyttTP89a709DSu7j2Vmlld6Osy/y1xMvEuPax373nyv3X6bfWsfDOqnsxLDEsLTEsLTF9LHsyLDIsLTEsLTF9LHstMSwwLDAsLTF9us17LTEsMSwxLC0xfaOss/XKvLuvyrHOqi0xo6y0+rHtuMPOu9bDw7vT0Mrc07DP7LXEVmlld6GjPC9wPgoKPHA+ytfPyLj5vt3NxsGmz/LBv7XEt73P8rzGy+O1scewytzTsM/stcSx373nus2xu83Gv6q1xHZpZXe1xNLGtq++4MDrcHVzaERpc3RhbmNlo6xWaWV3Q2x1c3RlcrXEc29ydENvbmZpZ3VyYXRpb25Gb3JFZGdlUHVzaLe9t6i4+b7dtbHHsLHfvee9q83Ptq/Kscrc07DP7LXEVmlld8XQts/PyLrzytzTsM/stcS0ztDyxcXQ8qGjzca2r8qxo6zF9rW9YWxsIGFwcLC0xaWjrLy0Y2FuUmVvcmRlcs6qZmFsc2WjrM3Gtq/Kp7DcoaPNxravyrGx6cD60tTFxbrD0PK1xMHQse2jrLX308Npc1ZpZXdUb3VjaGluZ0VkZ2W3vbeoxdC2z7Wxx7BWaWV3yse38b3TtKW1vbWxx7DK3NOwz+xWaWV3vK+jrHRydWXU8rzTvfjAtKOs0ru0zs3Gtq/Su7j2taXOu77gwOujrNK71rHNxrW9xL+x6s671sOjrM3Ptq/KsdOwz+y1xHZpZXe+zdXi0fnSu7LjsuPAqbTzobC40Mi+obGho83Ptq+94cr4uvOjrMXQts+94bn7yse38bOsuf2x373no6yyu7rPuPHU8nJlc3RvcmW3tbvYx7DXtMysoaM8L3A+Cgo8cD5hZGRWaWV3c1RvVGVtcExvY2F0aW9ut723qCA8YnI+CmJvdW5kaW5nUmVjdM6qy/nT0M/gudh2aWV3y/nVvLXEcmVjdMq508N1bmlvbre9t6jBqrrPtcRSZWN0o6zU2dPD0ru49mJvb2xlYW62/s6syv3X6bHtyr7G5L6ryLe1xNW8vt3H6b/2o6zU2bX308PJz8Pmy/nLtbXEZmluZE5lYXJlc3RBcmVh1tjU2Le9t6jH87P2wuTPwsf40/Kho2FkZFZpZXdUb1RlbXBMb2NhdGlvbre9t6jA4MvGoaM8L3A+Cgo8cD7G5Mv7RHJvcFRhcmdldLbUz/O6zVdvcmtzcGFjZbTzzazQodLso6y4+b7dsrvNrLXEx+m+sLmmxNy/ycTcz7i92snPu+HT0LK7zayhozwvcD4KCjxoMyBpZD0="數學基礎">數學基礎 數量積 isFlingingToDelete方法,findNearestArea方法,這裡用於判斷方向是否符合。
維基百科:https://zh.wikipedia.org/wiki/%E6%95%B0%E9%87%8F%E7%A7%AF 三角函數 computeDirectionVector方法,這裡計算具體的推動方向

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved