首页 > 代码库 > Android沉浸式状态栏攻略

Android沉浸式状态栏攻略

前言

这里不讨论[沉浸式]这个词用得好不好, 大家听得懂即可. 这篇文章主要是我在实际项目中的一些经验, 整理出来和大家分享, 欢迎探讨. 因为实习一直是996, 没时间做总结, 今天突然认为这种工作让我都忘了生活了, 是时候做个了断了. 写这篇文章的时候已经是23:44, 来不及贴一些demo, 可是这里的代码都是以前的项目中摘出来的, 是能够执行的, 但我如今没有真的执行一遍. 注意全部的代码都仅仅在android 4.4及以上有效.

考虑

依据实际项目的不同, 可能选择的沉浸式实现策略也会有所不同.

传统纯色actionbar

假设你的app使用的是遵循android规范的actionbar或者有一个纯色layout放在屏幕顶部, 那么这里有一个入侵较小的方案, 这个方法的实现方式是參考的开源项目SystemBarTint.
让你的activity继承一个BaseActivity, 在BaseActivity里面重写onPostCreate

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)  {
        ImmerseHelper.setSystemBarTransparent(this);
        }
    }

重点在ImmerseHelper这个类里, 先贴代码再解说.

public class ImmerseHelper {
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static void setSystemBarTransparent(Activity paramActivity)
    {
        Window window = paramActivity.getWindow();
        WindowManager.LayoutParams layoutParams = window.getAttributes();
        layoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
        window.setAttributes(layoutParams);
        hackStatusBarTransparent(paramActivity);
        setContentPadding(paramActivity);
    }

    public static void hackStatusBarTransparent(Activity paramActivity) {
        ViewGroup localViewGroup = (ViewGroup) paramActivity.getWindow().getDecorView()
                .findViewById(android.R.id.content);
        View colorview = new View(paramActivity);
        colorview.setBackgroundResource(R.color.statusbar_color);
        localViewGroup.addView(colorview, ViewGroup.LayoutParams.MATCH_PARENT,
                ImmerseHelper.getStatusBarHeight(paramActivity));
    }

    public static void setContentPadding(Activity activity) {
        ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0)
            .setPadding(0, ImmerseHelper.getStatusBarHeight(activity) 
                    + ImmerseHelper.getActionBarHeight(activity), 0, 0);
    }

    /**
     * 获取状态栏高度, 单位px
     * @param context
     * @return
     */
    public static int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    /**
     * 获取actionbar高度, 单位px
     * @param context
     * @return
     */
    public static int getActionBarHeight(Context context)
    {
        TypedValue localTypedValue = http://www.mamicode.com/new TypedValue();
        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, localTypedValue, true)) {
            return TypedValue.complexToDimensionPixelSize(localTypedValue.data, context.getResources().getDisplayMetrics());
        }
        return 0;
    }
}

简单来说就是在postCreate时对activity做手脚, 这样组内其它开发人员差点儿感受不到变化, 仅仅须要将activity的基类指定成BaseActivity就好
ImmerseHelper这个类主要做了三件事
- 将window的FLAG_TRANSLUCENT_STATUS标志打开
- 给某个View中加入了一个与状态栏大小全然同样的纯色块
- 给activity的根View设置paddingTop
FLAG_TRANSLUCENT_STATUS这个标志打开之后, 状态栏就透明了, 同一时候我们的activity的主体布局, 也就是setContentView传入的那个布局, 会顶到屏幕最上方, 被状态栏盖住一部分, 注意actionbar是不会受影响的. 所以我们在第三步给activity的根view设了paddingTop, 高度是状态栏的高度加上actionbar的高度, 这样activity中的内容才会回到之前的正常位置. 但此时状态栏下方透明了, 所以我们给某个View中加入了一个与状态栏大小全然同样的色块, 颜色和actionbar一致, 这样就有了沉浸式效果.
当然这里的效果是状态栏以下有一层半透明黑底, 之后才是我们加入的view, 所以不用操心看不到状态文字, 在4.4上仅仅能做到这个效果, 5.0上能够让状态栏底部全然透明, 这个等会儿说.
只是光有实现不行, 还须要知道为什么我们要这么做

原理

先看一下hierarchy view的截图

技术分享

我们getDecorView拿到的是最左边的DecorView, 而setContentView影响的是id/content那个view的直接子view, 和id/content平级的view是actionbar.
activity.getWindow().getDecorView().findViewById(android.R.id.content);这句拿到的是id/content这个view, 我们向当中加入一个纯色view, 因为打开了FLAG_TRANSLUCENT_STATUS, 这个纯色view就直接顶在最上面, 也就是状态栏覆盖的地方.
SystemBarTint中并不是是向id/content中addView, 而是直接向getDecorView()中addView, 而SwipeBackLayout则是在DecorView和他的子View之间插入自己的layout, 相当于劫持了DecorView的子View, 所以假设同一时候使用这两个开源项目不加改动, 要么滑动返回划走的是状态栏, 要么状态栏撕裂. 假设像我这么写, 就不会和SwipeBackLayout冲突.

非传统

技术分享

假设你要做到向上图这样, 图片全然顶在顶部, 那么就不建议使用actionbar了, 同一时候也不须要setContentPadding这步. 有时这样会导致某些页面的内容过分偏上, 这个时候建议用一个dimen, 在正常情况下是0dp, v19及以上时是24dp, 这也是状态栏高度, 哪些页面要隔开状态栏, 就用这个dimen做marginTop, 或者include一个高度为这个dimen的layout.
这样子的入侵比較强, 做新的页面须要时刻注意和状态栏是否须要保持距离, 非常easy忘记, 只是也不算什么困难, 毕竟一执行就看出来了.

Lolipop+特有方法

上面说的都是api level 19的方法, 唯一的缺陷是状态栏并不是全然透明, 而是底部有个半透明的黑条, 在api level 21上, 我们能够去掉这个半透明黑条, 让状态栏全然透明.
onCreate的setContentView调用之后, 将activity做參数传给以下这种方法, 就能够让你的app在api level 21上拥有全然透明的状态栏, 同一时候在api 19上使用上面的实现

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void setSystemBarTransparent(Activity paramActivity)
    {
        if (shouldUseTransparentSystemBar()) {
            Window window = paramActivity.getWindow();
            WindowManager.LayoutParams layoutParams = window.getAttributes();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                //api 21 解决方式
                View systemdecor = window.getDecorView();
            systemdecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
                layoutParams.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
                window.setStatusBarColor(0x00000000);
            } else {
                //api 19 解决方式
                layoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
            }
            window.setAttributes(layoutParams);
        }
    }

api 21的解决方式理论上是能够用xml完毕的, 可是我实际測试发现并不能, 仅仅实用代码才有效.
假设你想问api 20去哪了, 能够去看看sdk manager里面api 20的括号中写的什么.

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

Android沉浸式状态栏攻略