沉浸模式fitsSystemWindows

来源:互联网 发布:淘宝买狗可靠吗 编辑:程序博客网 时间:2024/04/19 19:37

本文参考自 云在千峰 的博客,查看原文请移步:http://blog.chengyunfeng.com/?p=905

根据最新的 Android 版本统计 ,4.4 以上的系统已经占有 70% 的份额了,现在新出厂的手机,只要不是500块钱以内的山寨手机也都是 5.0 系统了。所以是时候使用 fitsSystemWindows 属性了。

fitsSystemWindows 是在 android 4.4 中引入的。 System windows 顾名思义就是系统窗口,系统在这里显示系统一些属性和操作区域,比如 最上方的状态栏,以及没有实体按键的最下方的虚拟导航栏。

大部分的时候,你的应用都不会在状态栏和导航栏下面显示内容,如果你需要在他们下面显示内容,则需要确保你应用的可交互控件(比如按钮)不要显示在系统窗口下面了。 android:fitsSystemWindows=“true” 默认行为就是通过在 View 上设置和系统窗口一样高度的边框(padding )来确保你的内容不会出现到系统窗口下面。使用 fitsSystemWindows 有如下几点需要注意:

  • fitsSystemWindows 按照深度优先的方式其作用,所以使用该属性的 View 的顺序是有要求的,如果第一个 View 使用了 inset (系统窗口的尺寸)则会导致其他 View 尺寸不一样。
  • Inset 总是相对于全屏幕的,Inset 可能在 View layout 之前就已经应用了,所以在设置 View 的 padding 之前 View 并不知道其具体相对于系统窗口的位置。
  • View 的其他 padding 值被重新改写了,在使用 fitsSystemWindows 为 true 的View 上设置 padding 值(paddingLeft/paddingTop/ 等)是没有效果的。

比如 你想把 RecyclerView 的内容显示在一个透明导航栏的下面,就类似于 Google Now 一样,你可以在 RecyclerView 上设置 android:fitsSystemWindows=”true” ,然后在设置 RecyclerView 的 android:clipToPadding=”false”,这样这个 RecyclerView 就会显示在导航栏下方了,当你向上滑动 RecyclerView 到底的时候, RecyclerView 内容在导航栏上方,并没有被导航栏挡住。

关于 window insets

如果你查看 View 的 protected boolean fitSystemWindows (Rect insets) 函数,其参数值为 Rect insets, 关于该参数的说明如下:

Rect: Current content insets of the window. Prior to JELLY_BEAN you must not modify the insets or else you and Android will be unhappy.

返回值的说明如下:

true if this view applied the insets and it should not continue propagating further down the hierarchy, false otherwise.

content insets 是系统状态栏、导航栏、输入法等其他系统窗口所占用的空间,系统通过该回调接口告诉你的应用, 系统的窗口占用了多少空间,然后你可以根据该信息来调整你的 View 显示属性。

自定义 fitsSystemWindows

在 KitKat+ 版本中,你可以重写这个 protected boolean fitSystemWindows (Rect insets) 函数,如果一个 View 想吃掉这个 insets ,则返回 true,如果不想处理,让其他 View 来处理,则返回 false。

为了更方便的处理这种情况,在 Lollipop 版本,fitSystemWindows() 函数给废弃了,使用 两个新的函数来简化该处理流程。使用新的 onApplyWindowInsets() 函数,该函数中你可以选择吃掉一部分insets,然后你还可以调用 dispatchApplyWindowInsets() 函数让该 View 的子View 来继续处理 insets。

如果你只想在 5.0 以上版本使用该功能,则无需继承一个 View, 只需要使用 ViewCompat.setOnApplyWindowInsetsListener() 函数即可。使用 ViewCompat 提供的函数可以方便你编写版本兼容的代码,不需要频繁的判断版本号。

自定义 fitsSystemWindows 示例

系统的基本控件((FrameLayout, LinearLayout, 等)都使用默认的行为,Support 包中有些控件使用了自定义行为。

一个使用自定义行为的示例就是 侧边栏 ,侧边栏打开的时候,内容是占满整个屏幕高度的,状态栏显示为透明的,下面是 侧边栏的内容。

这里 DrawerLayout 使用 fitsSystemWindows 来表明需要处理 insets,但是仍然使用状态栏的颜色来绘制状态栏背景(状态栏颜色为 主题的 colorPrimaryDark 所设置的颜色)。

然后 DrawerLayout 在每个子 View 上调用 dispatchApplyWindowInsets() 函数,这样 子 View 也有 机会处理 insets,这和系统默认行为是不一样的(系统默认行为只是吃掉这个 insets,然后子 View 无法继续处理)。

CoordinatorLayout 对此也做了特殊处理,让每个子 View 的 Behavior 可以根据系统窗口的大小来做不同的处理。 还使用 fitsSystemWindows 属性来判断是否需要绘制状态栏背景。

通用 CollapsingToolbarLayout 也根据 fitsSystemWindows 属性来确定何时何地绘制 内容上方的半透明背景。

在 cheesesquare 示例项目中演示了这些 fitsSystemWindows 使用场景,可以下载该示例项目查看如何使用的。

Use the system, don’t fight it

注意这里并没有使用 fitsStatusBar 或者 fitsNavigationBar。组成系统窗口的元素以及他们的大小和位置在不同版本和不同设备上可能是会发生变化的,例如 在 Honeycomb 和 Ice Cream Sandwich 版本中就是不一样的。

在所有版本上, 使用 fitsSystemWindows 得到的值,可以根据该值来确保你的内容不会和系统窗口重叠。


总之最简单的使用方法,就这么几句代码就搞定:

@Overridepublicvoid onWindowFocusChanged(boolean hasFocus){super.onWindowFocusChanged(hasFocus);if(hasFocus){DecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE|View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}}

对DecorView不熟悉的朋友,可以读一下这篇文章:

Android View源码解读:浅谈DecorView与ViewRootImpl:http://www.jianshu.com/p/687010ccad66




2 1