天天看點

Launcher3 OverviewMode和mInsets

OverviewMode

Launcher3 OverviewMode和mInsets

Launcher3裡,在Workspace界面長按空白處,桌面進去OverviewMode,檢視源碼:Launcher類實作了View.OnLongClickListener接口,可以看到在onLongClick方法中有這麼一句:

mWorkspace.enterOverviewMode();
           

這句代碼在onLongClick方法中出現了兩次:

  • 在長按的view為Workspace且目前不在OverviewMode模式
  • 長按的地方為Hotseat圖示外的空白地方時,且目前不在拖動狀态和OverviewMode下

退出OverviewMode的方法是exitOverviewMode,通過檢視這兩個方法,它們都調用了enableOverviewMode方法。

看一下enableOverviewMode方法,這一句比較重要:

Animator workspaceAnim = getChangeStateAnimation(finalState, animated, , snapPage);
           

如同對象名workspaceAnim,這句代碼是取得Launcher3在各種狀态之間變化時的動畫,Workspace中有一個enum State,代表桌面的各種狀态,OverviewMode時State為State.OVERVIEW,顯示Workspace時狀态為State.NORMAL,getChangeStateAnimation方法第一個參數就是桌面即将進入的狀态。

getChangeStateAnimation方法比較重要,在桌面在各種狀态之間變化時都會調用,這裡看一下進入OverviewMode的動畫:一開始經過新老狀态boolean的判斷,決定背景透明度(finalBackgroundAlpha)、Hotseat和PageIndicator透明度(finalHotseatAndPageIndicatorAlpha)等相關屬性的設定和目前進行的狀态改變。然後取得桌面上各種“拼圖”:SearchBar,HotSeat,PageIndicator等對象并進行各種動畫效果,比如scale就是Workspace對象的縮放動畫。

LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
scale.scaleX(mNewScale)
     .scaleY(mNewScale)
     .translationY(finalWorkspaceTranslationY)
     .setDuration(duration)
     .setInterpolator(mZoomInInterpolator);
anim.play(scale);
...
           

mInsets

在計算動畫時各對象的屬性變化中,關于透明度,縮放的值的計算比較簡單,但是位置的計算通常要從DeviceProfile中取得資訊計算而來,比如Workspace縮放後的TranslationY在OverviewMode下由Workspace的getOverviewModeTranslationY方法得來。

看這個方法使用到了mInsets對象,看源代碼時一直想不明白這個值的意義,上網搜尋後才恍然大悟,可以參考這裡:LauncherRootView和DragLayer的布局過程。

這個網址打開較慢,摘錄一下相關的:

在主布局launcher.xml中, 最外層是LauncherRootView。

我們發現它隻是重寫了一個方法, View.fitSystemWindow(rect), 那這個方法是幹什麼的呢?查了官方文檔,還是看不懂他到底是做什麼的,但是倒是會用了。如果你的View在status bar或者navigation bar之下,那麼這個方法會傳遞進去一個參數,說明這個View的四周有多少是被status bar或者navigation bar擋住了的。這個方法的會調用父類的setInsets()方法,我們來看看父類InsettableFrameLayout。

InsettableFrameLayout繼承了FrameLayout, 并且實作了父類的一些方法,總的來說,他的目的就是給子View都設定一個Margin值,防止被status bar或者navigation bar擋住。如果一個View已經是Insettable的了,那麼就忽略他。在Launcher3中,Workspace, DragLayer和AppsCustomizeTabHost都實作了這個接口,那麼他們自身就不會被加Margin, 而搜尋欄和Hotseat都沒有實作這個接口,那麼說明他們在被加到父親上得時候會被設一個Margin,最後的到得效果就是搜尋欄剛好在status bar之下(gravity為top,topmargin是statusbar的高度),Hotseat剛好在navigationbar的上方(gravity為bottom,bottomMargin為navigation bar的高度)。

可以看一下InsettableFrameLayout的setFrameLayoutChildInsets方法

public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (child instanceof Insettable) {
            ((Insettable) child).setInsets(newInsets);
        } else if (!lp.ignoreInsets) {
            lp.topMargin += (newInsets.top - oldInsets.top);
            lp.leftMargin += (newInsets.left - oldInsets.left);
            lp.rightMargin += (newInsets.right - oldInsets.right);
            lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
        }
        child.setLayoutParams(lp);
    }
           

child是目前類的子類,如果其不是Insettable的子類,fitSystemWindow方法中傳進來的Rect就會當成Margin值加到LayoutParams中, 由于InsettableFrameLayout繼承了FrameLayout,加了這些Margin的View可以保證自己至少不會被狀态欄和導航欄遮擋。

如果child沒有繼承Insettable,如Workspace。Workspace重寫了setInsets方法,将fitSystemWindow方法傳遞進來的mInsets給自己的mInsets,這樣在計算布局的時候(例如上面提到的Workspace的getOverviewModeTranslationY方法),就可以考慮到狀态欄和導航欄的影響了。