跳至主要内容

android状态栏一体化(沉浸式状态栏)



Android-transulcent-status-bar



最近业务上看到一个设计图挺好看,所以研究了一下透明状态栏,注意不是沉浸式状态栏,在参考了网上的一些资料后,整理出了这篇博客.主要适用场景为一个Activity多个tab在不同状态栏模式下切换.
Github Demo 链接: StatusBarCompat

参考文章:

  1. 由沉浸式状态栏引发的血案
  2. Translucent System Bar 的最佳实践
  3. 该使用 fitsSystemWindows 了!

首先强调,对于状态栏的处理有两种不同的方式, 这里从Translucent System Bar 的最佳实践直接盗了两张图做对比~.
全屏( ContentView 可以进入状态栏)非全屏 ( ContentView 与状态栏分离, 状态栏直接着色)
先定义几个名词:
  1. 全屏模式: 左边图所示.
  2. 着色模式: 右边图所示.
  3. ContentViewactivity.findViewById(Window.ID_ANDROID_CONTENT) 获取的 View , 即 setContentView 方法所设置的 View, 实质为 FrameLayout.
  4. ContentParentContentView 的 parent , 实质为 LinearLayout.
  5. ChildViewContentView 的第一个子 View ,即布局文件中的 root layout .
再介绍一下相关的函数:
  1. fitsSystemWindows, 该属性可以设置是否为系统 View 预留出空间, 当设置为 true 时,会预留出状态栏的空间.
  2. ContentView, 实质为 ContentFrameLayout, 但是重写了 dispatchFitSystemWindows 方法, 所以对其设置 fitsSystemWindows 无效.
  3. ContentParent, 实质为 FitWindowsLinearLayout, 里面第一个 View 是 ViewStubCompat, 如果主题没有设置 title ,它就不会 inflate .第二个 View 就是 ContentView.
  4. FLAG_TRANSLUCENT_STATUS, 通过这个 Flag 可以设置状态栏透明(即没有状态栏).
  5. FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 5.0以上引入的 Flag, 通过这个 Flag 可以设置状态栏颜色.
  6. setStatusBarColor(), 5.0以上直接设置状态栏的方法,但是调用时必须设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS不能设置FLAG_TRANSLUCENT_STATUS.如果状态栏颜色是不透明的,设置SYSTEM_UI_FLAG_LAYOUT_STABLESYSTEM_UI_FLAG_LAYOUT_FULLSCREEN可以取消5.0上的状态栏阴影.
  7. requestApplyInsets(), 当窗口(Window)大小改变了,通知 View 去消费窗口的改变.

5.0以上的处理:

自5.0引入 Material Design ,状态栏对开发者更加直接,可以直接调用 setStatusBarColor 来设置状态栏的颜色.
全屏模式:
Window window = activity.getWindow();

window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
if (hideStatusBarBackground) {
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    window.setStatusBarColor(Color.TRANSPARENT);
    //隐藏状态栏的阴影window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else {
    window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}

ViewGroup mContentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
    ViewCompat.setOnApplyWindowInsetsListener(mChildView, new OnApplyWindowInsetsListener() {
        @Override
        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
            return insets;
        }
    });
    ViewCompat.setFitsSystemWindows(mChildView, false);
    ViewCompat.requestApplyInsets(mChildView);

}
着色模式:
Window window = activity.getWindow();

window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(statusColor);
//清除全屏模式下的 Flag
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);

ViewGroup mContentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
    ViewCompat.setOnApplyWindowInsetsListener(mChildView, new OnApplyWindowInsetsListener() {
        @Override
        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
            return insets;
        }
    });
    ViewCompat.setFitsSystemWindows(mChildView, true);
    ViewCompat.requestApplyInsets(mChildView);
}

4.4-5.0的处理:

4.4-5.0因为没有直接的 API 可以调用,需要自己兼容处理.参考了网上的解决方法及结合我自己遇到的坑,最后想出的解决办法如下:
着色模式
  1. 向 DecorView 中添加一个 View, 高度为状态栏的高度(反射获取).
  2. 将 ChildView 的 marginTop 加上状态栏的高度,以此来模拟 fitsSystemWindows.
  3. 设置 ChildView 的 fitsSystemWindow 为 false, 不预留系统栏位置.
  4. 为 DecorView 设置一个 tag, 防止重复添加 View.
这里与其他地方不同的是:
  1. 向 ContentView 添加 View 在部分机型(华为)上没有效果.
  2. 向 ContentParent 上添加 View 会有一条黑线.
  3. 使用 marginTop 而不是 fitsSystemWindows 是因为无法在不重启 Activity 的情况下切换 root layout 的fitsSystemWindows属性, 即直接设置不会生效, 所以用 marginTop 来模拟.
全屏模式
  1. 设置 ChildView 的 fitsSystemWindow 为 false, 不预留系统栏位置.
  2. 如果在 ChildView 的 marginTop 中添加了状态栏的高度, 则移除.
  3. 设置 tag, 防止重复移除.
CollaspingToolbarLayout 
这个 support 包中的控件, 由于重写了 onApplySystemInsets() 方法, 按照我所理解的状态栏模式, 它在滑动时在两种模式中切换, 对此我的兼容方法就是让其处于 着色模式 下,在滑动时保持状态栏颜色不变. 当然有更好的解决办法, 但我这里为了方便调用(只需要传递 Activity 对象), 就用了比较简单的处理方法.

关于博客和库

博客主要提供思路解析,因为博客的通知不及时,大家有问题有想法想交流时还请在 Github issues 页联系.

评论

此博客中的热门博文

onsen ui example splitter side menu swipe

<!DOCTYPE html> <html> <head> <title>TheyTube - Watch free video online</title> <link rel="stylesheet" href="https://unpkg.com/onsenui/css/onsenui.css"> <link rel="stylesheet" href="https://unpkg.com/onsenui/css/onsen-css-components.min.css">   <script src="https://unpkg.com/onsenui/js/onsenui.min.js"></script>   <script type="text/javascript">   ons.platform.select('android')   </script> </head> <body> <ons-splitter>   <ons-splitter-side id="menu" side="left" width="220px" collapse swipeable>     <ons-page>       <ons-list>         <ons-list-item onclick="fn.load('home.html')" tappable>           Home         </ons-list-item>         <ons-list-item onclick="fn.load('settings.html')" tappable>           Setti

go golang get disk usage free space remain info

package main import ( "fmt" "syscall" ) func main() { fmt.Println(DiskUsage("./")) } func DiskUsage(path string) uint64 { fs := syscall.Statfs_t{} err := syscall.Statfs(path, &fs) if err != nil { return 0 } return fs.Bfree * uint64(fs.Bsize) } //All space   = fs.Blocks * uint64(fs.Bsize) //Free space = fs.Bfree * uint64(fs.Bsize) //Used space= fs.All - disk.Free