zoukankan      html  css  js  c++  java
  • 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不处理就可以。

  • 相关阅读:
    手机端和电脑端左右分屏录制视频解决方法
    收藏 网站部署配置文章
    廖雪峰网站:学习python函数—递归函数(四)
    廖雪峰网站:学习python函数—函数参数(三)
    廖雪峰网站:学习python函数—定义函数(二)
    廖雪峰网站:学习python函数—调用函数(一)
    廖雪峰网站:学习python基础知识—循环(四)
    廖雪峰网站:学习python基础知识—判断(三)
    Java提高十七:TreeSet 深入分析
    Java提高十六:TreeMap深入分析
  • 原文地址:https://www.cnblogs.com/muouren/p/11706231.html
Copyright © 2011-2022 走看看