专栏首页老欧说安卓Android开发笔记(一百六十三)高仿京东的沉浸式状态栏

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开发笔记的完整目录

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android开发笔记(一)像素的单位

    老子曾说“天下难事必作于易,天下大事必作于细”,其实Android开发也是如此。博主一开始学android的时候,对像素单位不知其所以然,只知一根筋的填数字...

    用户4464237
  • Android开发笔记(四十二)Broadcast的生命周期

    广播(Broadcast)用于Android组件之间的灵活通信,它与Activity和Service的区别在于: 1、Activity和Service都只...

    用户4464237
  • Android开发笔记(九十四)图片的基本加工

    Android上的图形使用Drawable类,而位图管理则使用Bitmap类,java上与之对应的是awt包中的BufferedImage。Android开...

    用户4464237
  • iOS 知识小集(Status Bar变换)

    iOS 中经常会有需要在某个界面改变状态栏颜色或者某个界面隐藏状态栏的需求。而改变状态栏颜色和控制状态栏显示和隐藏的API,在iOS 的不同版本中也发生了很多变...

    Haley_Wong
  • UVA 10003 Cutting Sticks(区间DP)

    题意: 有一根长度为l的木棍,木棍上面有m个切割点,每一次切割都要付出当前木棍长度的代价,问怎样切割有最小代价 在完成这道题目之前,我们先熟悉一下区间DP 对于...

    用户1624346
  • Gradle 6 针对已有的构建如何创建一个构建扫描

    构建扫描(build scan)是一个中心化并且可以共享的构建记录。这个构建记录通常能够告诉在构建中发生了什么并且为什么会发生。

    HoneyMoose
  • 剑指offer【03~09】

    注意:这道题如果数据范围变为 [1, n],那么可以将其转化为使用快慢指针判断有环问题,和 Leetcode 【Two Pointers】287. Find t...

    echobingo
  • Linux下安全扫描工具Nmap用法详解

    扫描器是一种能够自动检测主机安全性弱点的程序。扫描器通过发送特定的网络数据包,记录目标主机的应答消息,从而收集关于目标主机的各种信息。目前网络上有很多扫描软件,...

    joshua317
  • DIY简易Python脚本调用AWVS扫描

    前言 最近写了一个小系统,需要调用AWVS扫描工具的API接口实现扫描,在网上只搜到添加任务和生成报告的功能实现代码,无法添加扫描对象登录的用户名和密码,如果不...

    FB客服
  • OC中的nil、Nil、NULL、NSNull的区别

    nil是指一个不存在的OC实例对象指针,指的是OC实例对象指针的空值,也就是OC实例对象的空指针。

    拉维

扫码关注云+社区

领取腾讯云代金券