DrawerLayout中側滑菜單沉浸式原理


DrawerLayout側滑菜單沉浸式分析

接着android6.0 SystemUi分析,來分析一下drawerlayout

DrawerLayout要想到達側滑菜單沉浸式,就需要在DrawerLayout布局中加入:

android:fitsSystemWindows="true"

這樣系統在向下傳遞insets時就會傳遞給DrawerLayout。

DrawerLayout在構造函數中做了一些特殊特處理:

    public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        ...
        if (ViewCompat.getFitsSystemWindows(this)) {
            IMPL.configureApplyInsets(this);
            mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
        }
        ...
    }

 IMPL.configureApplyInsets(this);

對api21以下的不做任何處理(即空方法),

對api21及以上的:

DrawerLayoutCompatApi21:

    public static void configureApplyInsets(View drawerLayout) {
        if (drawerLayout instanceof DrawerLayoutImpl) {
            drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener());
            drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
        }
}

 可見DrawerLayout會自動加入View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN。那么mContentRoot就不會消費。

View.setOnApplyWindowInsetsListener會使在根view向下傳遞insets時不調用view的onApplyWindowInsets,而調用監聽的onApplyWindowInsets。

   static class InsetsListener implements View.OnApplyWindowInsetsListener {
        @Override
        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
            final DrawerLayoutImpl drawerLayout = (DrawerLayoutImpl) v;
            drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0);

            // 直接consume掉
            return insets.consumeSystemWindowInsets();
        }
}

  

DrawerLayout:

   @Override
    public void setChildInsets(Object insets, boolean draw) {
        mLastInsets = insets;
        mDrawStatusBarBackground = draw;
        setWillNotDraw(!draw && getBackground() == null);
        requestLayout();
}

 在setChildInsets中會把insets保存下來,

用在DrawerLayout.onMeasure時使用:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...

        // 判斷是否要處理insets
        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
        ...

        // 會對drawerlayout的content和drawer都應用insets
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (applyInsets) {
                final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection);
                
                // 如果子view設置FitsSystemWindows為true則會傳遞給子view讓其消費,
                // 如果是false,則會對子view設置margin值來適配systemui(statusbar等)
                if (ViewCompat.getFitsSystemWindows(child)) {
                    IMPL.dispatchChildInsets(child, mLastInsets, cgrav);
                } else {
                    IMPL.applyMarginInsets(lp, mLastInsets, cgrav);
                }
            }
            ...
        }
    }

 

 下邊的兩個方法也是只對api21以上的有處理邏輯

IMPL.dispatchChildInsets

  public static void dispatchChildInsets(View child, Object insets, int gravity) {
        WindowInsets wi = (WindowInsets) insets;
        if (gravity == Gravity.LEFT) {
            wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(),
                    wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom());
        } else if (gravity == Gravity.RIGHT) {
            wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(),
                    wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom());
        }
        child.dispatchApplyWindowInsets(wi);
}

 

IMPL.applyMarginInsets

    public static void applyMarginInsets(ViewGroup.MarginLayoutParams lp, Object insets, int gravity) {
        WindowInsets wi = (WindowInsets) insets;
        if (gravity == Gravity.LEFT) {
            wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(),
                    wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom());
        } else if (gravity == Gravity.RIGHT) {
            wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(),
                    wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom());
        }
        lp.leftMargin = wi.getSystemWindowInsetLeft();
        lp.topMargin = wi.getSystemWindowInsetTop();
        lp.rightMargin = wi.getSystemWindowInsetRight();
        lp.bottomMargin = wi.getSystemWindowInsetBottom();
    }

 由上可知如果Drawerlayout的drawer是一個ListView/RecyclerView的話,設置如下屬性:

android:clipToPadding="false"
android:fitsSystemWindows="true" 

 就可以是抽屜view在滑出的時候達到沉浸式的效果。

 

NavigationView沉浸式分析

<android.support.design.widget.NavigationView
        android:id="@+id/navigation"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="@color/white"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/activity_main_drawer" />

 

如果在DrawerLayout中使用NavigationView的話不用加入

android:clipToPadding="false"
android:fitsSystemWindows="true" 

 

也可以直接使用,原因肯定是內部進行了設置。

 

NavigationView extends ScrimInsetsFrameLayout

ScrimInsetsFrameLayout extends FrameLayout

 

在NavigationView 構造函數中:

ViewCompat.setFitsSystemWindows(this,
                a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false));

  系統默認會設置fitsSystemWindows為true。

 在ScrimInsetsFrameLayout 構造函數中:

ViewCompat.setOnApplyWindowInsetsListener(this,
                new android.support.v4.view.OnApplyWindowInsetsListener() {
                    @Override
                    public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                        if (null == mInsets) {
                            mInsets = new Rect();
                        }
                        mInsets.set(insets.getSystemWindowInsetLeft(),
                                insets.getSystemWindowInsetTop(),
                                insets.getSystemWindowInsetRight(),
                                insets.getSystemWindowInsetBottom());
                        onInsetsChanged(insets);
                        setWillNotDraw(mInsets.isEmpty() || mInsetForeground == null);
                        ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this);

                        return insets.consumeSystemWindowInsets();
                    }
                });

 

 

對insets的dispatch做了監聽,那么接着上邊的dispatchChildInsets,

會調用ScrimInsetsFrameLayout 監聽的onApplyWindowInsets方法。

在NavigationView .onInsetsChanged中

    @Override
    protected void onInsetsChanged(WindowInsetsCompat insets) {
        mPresenter.dispatchApplyWindowInsets(insets);
    }

  

NavigationMenuPresenter會對側滑菜單中的header和menu進行初始化和創建添加view。

包含兩個view:

LinearLayout mHeaderLayout;

NavigationMenuView mMenuView;

 

mMenuView實際上是RecyclerView,他的布局是:

    <android.support.design.internal.NavigationMenuView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/design_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/design_navigation_padding_bottom"
        android:clipToPadding="false"
        android:scrollbars="vertical"/>

  

mHeaderLayout就是mMenuView中viewType為VIEW_TYPE_HEADER的itemview。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/navigation_header_container"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      android:paddingBottom="@dimen/design_navigation_separator_vertical_padding" />

  

NavigationMenuPresenter:

    public void dispatchApplyWindowInsets(WindowInsetsCompat insets) {
        int top = insets.getSystemWindowInsetTop();
        if (mPaddingTopDefault != top) {
            mPaddingTopDefault = top;
            if (mHeaderLayout.getChildCount() == 0) {
                mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom());
            }
        }
        ViewCompat.dispatchApplyWindowInsets(mHeaderLayout, insets);
}

 

可以看到mHeaderLayout.getChildCount() == 0的判斷肯定為false,所以就直接把insets傳遞給headerview,

而headerview也是沒有設置fitsSystemWindows,所以也不會處理,

也就是說沒有view消費insets,所以其實上邊clipToPadding可以不設置為false也行。

看起來好像並沒有對insets進行消費,那為什么還能達到沉浸效果,主要是因為DrawerLayout在初始化時就設置了可以擴展到狀態欄,但如果drawer不設置fitsystemwindow=true,就會交給DrawerLayout用margin進行處理了,所以用fitsystemwindow=true拿來讓NavigationView進行處理,NavigationView不處理就可以。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM