Android开发笔记(一百六十三)高仿京东的沉浸式状态栏

前面的文章介绍了如何实现广告轮播的Banner效果,本想可以告一段落。然而某天产品经理心血来潮,拿着苹果手机,要求像iOS那样把广告图顶到状态栏这儿。刚接到这需求,不禁倒吸一口冷气,又要安卓开发去实现iOS的效果,真是强人所难。翻了翻资料,发现修改状态栏的颜色倒是可行,但要把轮播图顶上去就不容易了。再瞅瞅淘宝和当当,原来两个大厂的App都没做出这个效果。正想跟产品经理说这个实现不了,谁料产品大姐笑盈盈地走过来,指着手机说道:“你看,做成京东这样就行了。”盯着手机看了半晌,京东这厮还真的让轮播图插进状态栏了,于是瞬间石化。下面是京东App的首页头部截图:


每当此时,便是程序员最煎熬的时候,人家都做得,为啥你做不得?只好继续寻寻觅觅,又找到另一个电商App,它在Android6.0手机上也完美实现了状态栏悬浮效果,但是在Android4.4手机运行时仍然没能覆盖状态栏。可见这真不是一个省油的灯,许多人用的App尚且未能解决悬浮状态栏的兼容性问题。该电商App的首页截图如下所示,其中左图为Android6.0手机上的运行界面,此时状态栏浮在轮播图上面;右图为Android4.4手机的运行界面,此时状态栏依旧与轮播图泾渭分明。



早期的Android版本姑且不提,Android迟至4.4才开始支持沉浸式状态栏,编码的时候通过Window对象的setAttributes方法来设置窗口属性的标志位。其中标志位WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS用于控制顶部状态栏是否透明,标志位WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION用于控制底部导航栏是否透明。具体的实现代码如下所示:
        // Android4.4的沉浸式状态栏写法
        Window window = activity.getWindow();
        WindowManager.LayoutParams attributes = window.getAttributes();
        int flagTranslucentStatus = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
        // 底部导航栏也可以弄成透明的
        //int flagTranslucentNavigation = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
        attributes.flags |= flagTranslucentStatus;
        //attributes.flags |= flagTranslucentNavigation;
        window.setAttributes(attributes);
到了Android5.0之后版本,系统允许直接定制状态栏的颜色,例如调用Window对象的setStatusBarColor方法即可设置顶部状态栏的背景色,调用Window对象的setNavigationBarColor方法即可设置底部导航栏的背景色。不过状态栏的悬浮开关发生了变化,要想让状态栏变透明,最新的方式是调用DecorView对象的setSystemUiVisibility方法来设置标志位。详细的标志位设置代码如下所示:
        // Android5.0之后的沉浸式状态栏写法
        Window window = activity.getWindow();
        View decorView = window.getDecorView();
        // 两个标志位要结合使用,表示让应用的主体内容占用系统状态栏的空间
        // 第三个标志位可让底部导航栏变透明View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        decorView.setSystemUiVisibility(option);
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
然而以上的处理过程只解决了事情的一个方面,即成功将状态栏悬浮在主页面之上,或者说将主页面沉没到状态栏之下。可是事情的另一方面——把悬浮着的状态栏恢复原状——并没有得到解决,甚至给状态栏换个背景色都不行。譬如说乘船过河,Android时常派了渡船运送乘客,可是当你到达彼岸之后,却发现回程的船只不见了踪影。就恢复状态栏的原状而言,设置标志位是行不通的,幸好过河不一定靠船,还有一招叫做瞒天过海。虽然主页面已经和状态栏重叠在了一起,没法强行把它俩拆散,但我们可以叫主页面让一让,不要跟状态栏挨得这么紧,就是给主页面设置一段顶端空白topMargin,表示主权在我、不妨让你三尺,于是主页面让出一段空白,看起来就与状态栏井水不犯河水了。如此一来,状态栏的悬浮和恢复操作便是可逆的了,如果移除主页面的顶端空白,状态栏就产生悬浮效果;如果添加主页面的顶端空白,状态栏就恢复原状。
对于Android4.4,情况还会更加特殊,因为系统没有提供设置状态栏颜色的方法,所以只能手工搞个假冒的状态栏来占坑。先将这个冒牌状态栏(其内部没有别的控件)染上开发者指定的颜色,然后与系统自带的状态栏重合,于是乎偷梁换柱仿佛给状态栏换了一件衣裳。修改之后的状态栏背景设置代码如下所示(兼容Android4.4,以及5.0以上版本这两种情况):
    // 重置状态栏。即把状态栏颜色恢复为系统默认的黑色
    public static void reset(Activity activity) {
        setStatusBarColor(activity, Color.BLACK);
    }

    // 设置状态栏的背景色。对于Android4.4和Android5.0以上版本要区分处理
    public static void setStatusBarColor(Activity activity, int color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                activity.getWindow().setStatusBarColor(color);
                // 底部导航栏颜色也可以由系统设置
                //activity.getWindow().setNavigationBarColor(color);
            } else {
                setKitKatStatusBarColor(activity, color);
            }
            if (color == Color.TRANSPARENT) { // 透明背景表示要悬浮状态栏
                removeMarginTop(activity);
            } else { // 其它背景表示要恢复状态栏
                addMarginTop(activity);
            }
        }
    }

    private static final String TAG_FAKE_STATUS_BAR_VIEW = "statusBarView";
    private static final String TAG_MARGIN_ADDED = "marginAdded";
    // 添加顶部间隔,留出状态栏的位置
    private static void addMarginTop(Activity activity) {
        Window window = activity.getWindow();
        ViewGroup contentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
        View child = contentView.getChildAt(0);
        if (!TAG_MARGIN_ADDED.equals(child.getTag())) {
            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) child.getLayoutParams();
            // 添加的间隔大小就是状态栏的高度
            params.topMargin += getStatusBarHeight(activity);
            child.setLayoutParams(params);
            child.setTag(TAG_MARGIN_ADDED);
        }
    }

    // 移除顶部间隔,霸占状态栏的位置
    private static void removeMarginTop(Activity activity) {
        Window window = activity.getWindow();
        ViewGroup contentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
        View child = contentView.getChildAt(0);
        if (TAG_MARGIN_ADDED.equals(child.getTag())) {
            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) child.getLayoutParams();
            // 移除的间隔大小就是状态栏的高度
            params.topMargin -= getStatusBarHeight(activity);
            child.setLayoutParams(params);
            child.setTag(null);
        }
    }

    // 对于Android4.4,系统没有提供设置状态栏颜色的方法,只能手工搞个假冒的状态栏来占坑
    private static void setKitKatStatusBarColor(Activity activity, int statusBarColor) {
        Window window = activity.getWindow();
        ViewGroup decorView = (ViewGroup) window.getDecorView();
        // 先移除已有的冒牌状态栏
        View fakeView = decorView.findViewWithTag(TAG_FAKE_STATUS_BAR_VIEW);
        if (fakeView != null) {
            decorView.removeView(fakeView);
        }
        // 再添加新来的冒牌状态栏
        View statusBarView = new View(activity);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
        params.gravity = Gravity.TOP;
        statusBarView.setLayoutParams(params);
        statusBarView.setBackgroundColor(statusBarColor);
        statusBarView.setTag(TAG_FAKE_STATUS_BAR_VIEW);
        decorView.addView(statusBarView);
    }
总算大功告成,接着看看实际的运行效果,具体如下图所示。由于上述代码同时兼容Android4.4,以及5.0以上版本这两种情况,因此就不重复贴图了。其中左图为悬浮状态栏的效果图,右图为恢复状态栏的效果图。




点此查看Android开发笔记的完整目录


__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

猜你喜欢

转载自blog.csdn.net/aqi00/article/details/79374532
今日推荐