随手记Android沉浸式状态栏的踩坑之路

欢迎关注微信公众号「随手记技术团队」,查看更多随手记团队的技术文章。
本文做者:刘玲
原文连接:mp.weixin.qq.com/s/d6D_rYmzl…android

1-前言

关于“沉浸式状态栏”这种叫法,有的朋友可能会以为不妥。可是目前网上大部分讲到“沉浸式状态栏”基本都是指“透明状态栏”,因此这里就不讨论其对错了(其实有时候错的多了,也就成了对的了),你们知道是说的“透明状态栏”就好了,下文都是称这种效果为“沉浸式状态栏”。windows

在Android 4.4以前,全部应用都是没法设置状态栏的背景颜色的,都是跟着系统来的(黑色背景状态栏),一块黑色的状态栏和应用很是不搭调。为了提供更好的交互效果,Google在Android 4.4以后提供了设置沉浸式状态栏的方法。支持沉浸式状态栏的App的界面显得逼格更高一点,所以随手记Android客户端也在年初的时候也支持了沉浸式状态栏。在实现沉浸式状态栏效果的过程当中踩了很多的坑,特此记录下来。 下图为随手记Android客户端设置沉浸式状态栏先后的效果对比图:bash

随手记沉浸式和非沉浸式对比图

对比两种效果,很明显下面设置了沉浸式状态栏的看上去更协调、更美观一点。微信

2-如何实现沉浸式状态栏

2.1-Android 4.4以上实现方式

因为沉浸式状态栏设置是在Android 4.4以后才提供的,因此咱们须要对Android 4.4以上的系统作适配。Android 4.4有两种方式能够实现沉浸式状态栏,一种是在资源文件中设置,一种是在代码中设置。app

2.1.1-资源文件中设置沉浸式状态栏

首先,咱们要修改values/styles.xml,在里面添加一个空的style,继承自BaseTheme。ide

<resources>
    <!-- Base application theme. -->
    <style name="BaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme" parent="BaseTheme" />
</resources>
复制代码

而后在values-v19目录下的styles.xml文件(若是项目中没有就新建一个,在4.4以上的系统就会读取该目录下的资源文件)添加以下代码:布局

<resources>
    <style name="AppTheme" parent="BaseTheme">
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>
复制代码

而后将App的主题设置为AppTheme便可。 注:android:windowTranslucentStatus这个属性是v19开始引入的。测试

2.1.2-在代码中设置

在代码中实现更为方便一点,咱们只须要在BaseActivity中添加一个FLAG_TRANSLUCENT_STATUS的flag便可。字体

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
复制代码

经过上述两种方法设置以后,效果图以下:ui

ToolBar顶上去

咱们会发现,仅仅经过上述设置Toolbar会顶到状态栏里面去。一般你们会想到使用fitsSystemWindows属性来解决此问题。

fitSystemWindows官方描述:Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity. 简单描述:这个属性的做用是让view能够根据系统窗口(如status bar)来调整本身的布局,若是值为true,就会调整view的paingding属性来给system windows留出空间(即给view添加一个值为状态栏高度的top padding)。

咱们试着给Toolbar设置一下fitsSystemWindows属性为true。布局代码以下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 
    <android.support.v7.widget.Toolbar
        android:id="@+id/my_toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
 
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:padding="16dp">
 
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#e0e0e0"
            android:layout_gravity="bottom">
            <EditText
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:fitsSystemWindows="true"
                android:background="@drawable/edit_text_rect_bg" />
        </RelativeLayout>
    </FrameLayout>
</LinearLayout>
复制代码

上面代码在Android 4.4和Android 5.0+上面对比效果图以下:

4.4和5.0对比效果

由上面对比图咱们能够看出来,在Android 4.4上面状态栏是全透明的,而在Android 5.0+上面状态栏是半透明的。

注:有些4.4的系统上面状态栏并非全透明的,而是渐变的。

2.2-Android 5.0以上实现方式

上面已经实现了沉浸式状态栏的效果了,可是若是运行在Android 5.0以上的机器上面,会发现大部分手机会出现状态栏是半透明的。

也有些App在Android 5.0以上就是这种状态栏半透明的效果,好比QQ。可是有些产品和设计就是想统一风格,所有都实现全透明的状态栏。那怎么办呢?Android自5.0起,又为咱们提供了设置状态栏颜色的API,咱们能够本身设置状态栏的颜色。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
    window.setStatusBarColor(Color.TRANSPARENT);
 
}
复制代码

添加上述代码后再在Android 5.0+上运行看效果,状态栏已经变成全透明了,和上图Android 4.4效果同样的,这里就再也不附图了。

2.3-Android 6.0以上设置状态栏字体颜色

大部分手机默认状态栏字体颜色是白色的,若是Toolbar或者界面头部的颜色较浅,那么状态栏上白色的字看不怎么清楚。 Android 6.0之后,咱们可使用代码将状态栏字体的颜色设置为黑色了,代码以下:

window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
复制代码

3-踩过的坑

本觉得上面基本已经完美实现了沉浸式状态栏了,没想到测试的时候仍是发现了一系列的坑。

3.1-软键盘弹出时Toolbar被顶上去了

若是在界面中有EditText或者其余输入框的话,会发现当软件盘弹出的时候Toolbar里面的内容都被顶上去了,以下图所示:

ToolBar被软键盘顶上去

这是为何呢?经研究发现原来是fitsSystemWindows属性搞的鬼。哪一个View设置了fitsSystemWindows=true,这个View就会被软件盘顶上去。因此说,fitsSystemWindows不能乱用,会有意想不到的坑。 那能不能不用fitsSystemWindows呢?固然能够。前面也说了,fitsSystemWindows=true的做用是给View增长值为状态栏高度的padding,那咱们何不本身手动给Toolbar添加padding呢? 咱们去掉Toolbar上的fitsSystemWindows属性,并设置一下Toolbar的padding,代码以下:

protected void setStatusBarPaddingAndHeight(View toolBar) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        if (toolBar != null) {
            int statusBarHeight = getSystemBarHeight(this);
            toolBar.setPadding(toolBar.getPaddingLeft(), statusBarHeight, toolBar.getPaddingRight(),
                    toolBar.getPaddingBottom());
            toolBar.getLayoutParams().height = statusBarHeight +
                    (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics());
        }
    }
}
复制代码

去掉Toolbar的fitsSystemWindows属性,并加上上面的代码,软键盘弹出时Toolbar正常了。 目前随手记Android项目中就是使用代码添加padding的方式替代fitsSystemWindows属性的。

3.2-软键盘弹出时EditText等输入框会被软件盘覆盖掉

上面软件盘将Toolbar顶上去的示例图中,咱们还会发现一个问题,就是软键盘弹出时EditText并无跟着弹出来而是被软键盘覆盖掉了。

上面说Toolbar加了fitsSystemWindows属性以后会被软键盘顶上去,那么咱们给输入框加一个fitsSystemWindows属性是否恰好就能解决输入框被覆盖的问题呢?果断试一下!

输入框有padding

试了以后发现,果真能够,可是输入框的高度变了,实际上是输入框的padding增长了状态栏的高度。很显然,这并非一个很好的解决方式。 后来在stackoverflow上找到了一个解决方法:解决FLAG_TRANSLUCENT_STATUS致使输入框被软键盘覆盖的解决方案

咱们对其作了点调整,代码以下:

public class AndroidBug5497Workaround {

    public static void assistActivity(View content) {
        new AndroidBug5497Workaround(content);
    }

    private View mChildOfContent;
    private int usableHeightPrevious;
    private ViewGroup.LayoutParams frameLayoutParams;

    private AndroidBug5497Workaround(View content) {
        if (content != null) {
            mChildOfContent = content;
            mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                public void onGlobalLayout() {
                    possiblyResizeChildOfContent();
                }
            });
            frameLayoutParams = mChildOfContent.getLayoutParams();
        }
    }

    private void possiblyResizeChildOfContent() {
        int usableHeightNow = computeUsableHeight();
        if (usableHeightNow != usableHeightPrevious) {
            //若是两次高度不一致
            //将计算的可视高度设置成视图的高度
            frameLayoutParams.height = usableHeightNow;
            mChildOfContent.requestLayout();//请求从新布局
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight() {
        //计算视图可视高度
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        return r.bottom;
    }

}
复制代码

添加上面的类,而后在Activity的onCreate方法中的setContentView后面加上以下代码:

AndroidBug5497Workaround.assistActivity(findViewById(android.R.id.content));
复制代码

而后运行,输入框可以正常被顶上去,并且输入框的布局又没有受到影响。

软键盘正常弹出

该方案的原理是,给界面的根布局设置一个监听器,当界面大小有变化的时候,如键盘弹出的时候,从新设置一下根布局的高度,再调用requestLayout对界面进行重绘。

目前随手记Android就是使用这个方案,截止到目前也没有发现这种方案会带来其余什么问题。

3.3-华为EMUI3.1上的坑

将上面的沉浸式代码放在EMUI3.1系统的手机(如华为荣耀7)上面跑,会发现根本没有沉浸式效果,状态栏是透明的,显示的是桌面上的颜色,以下图:

EMUI3.1沉浸式问题

经验证,原来是EMUI3.1系统的缘由,不少App也是在EMUI3.0上有沉浸式的效果,到了EMUI3.1却没有效果了。在EMUI3.1没有沉浸式效果若是和4.4之前同样是黑的也就算了,这样透明的显示桌面颜色实在难看。 后来发现去掉下面这句代码,可让其有沉浸式的效果。

window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
复制代码

效果以下:

EMUI3.1沉浸式问题修复后

不过它的状态栏不是全透明的,而是像某些4.4的系统同样是渐变的,不过总比显示桌面颜色的效果好。 这里咱们加一个判断,判断若是不是EMUI3.1的系统,才调用clearFlags清除掉FLAG_TRANSLUCENT_STATUS。 具体代码以下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
   Window window = getWindow();
   window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      // 由于EMUI3.1系统与这种沉浸式方案API有点冲突,会没有沉浸式效果。
      // 因此这里加了判断,EMUI3.1系统不清除FLAG_TRANSLUCENT_STATUS
      if (!isEMUI3_1()) {
         window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
      }
      window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
      window.setStatusBarColor(Color.TRANSPARENT);
   }
}
 
public static boolean isEMUI3_1() {
    if ("EmotionUI_3.1".equals(getEmuiVersion())) {
        return true;
    }
    return false;
}
 
private static String getEmuiVersion(){
    Class<?> classType = null;
    try {
        classType = Class.forName("android.os.SystemProperties");
        Method getMethod = classType.getDeclaredMethod("get", String.class);
        return (String)getMethod.invoke(classType, "ro.build.version.emui");
    } catch (Exception e){
    }
    return "";
}
复制代码

3.4-CoordinatorLayout+AppBarLayout滚动隐藏导航栏遇到沉浸式状态栏的坑

这个坑主要是在作理财头条需求的时候碰到的。

需求背景:头条功能须要实现二级TabLayout导航,第一级是Toolbar(头条、产品和发现),第二级是是头条里面各个栏目切换的TabLayout。须要实现的效果是,在头条Fragment中,滑动帖子列表能够隐藏和显示一级导航Toolbar。一级导航Toolbar显示的时候,左右滑动是切换一级导航的Tab(即头条、发现和产品)。当在头条Fragment中上滑滚动帖子列表隐藏一级导航Toolbar后,左右滑动是切换二级导航的tab(即头条各个栏目)。效果见下图。

理财头条效果1

滚动列表隐藏和显示Toolbar,首先确定是想到CoordinatorLayout+AppBarLayout。基于项目中已实现的沉浸式效果,添加修改Activity中的布局:

<android.support.design.widget.CoordinatorLayout
   android:id="@+id/coordinator_layout"
   android:layout_height="match_parent"
   android:layout_width="match_parent">
   <android.support.design.widget.AppBarLayout
       android:id="@+id/appbar_layout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:elevation="0dp">
       <android.support.v7.widget.Toolbar
           android:layout_width="match_parent"
           android:layout_height="?attr/actionBarSize"
           app:contentInsetStart="0dip"
           app:contentInsetLeft="0dip"
           app:contentInsetEnd="0dip"
           app:layout_scrollFlags="scroll|enterAlways">
           ...部分代码省略...
          <android.support.design.widget.TabLayout
              android:id="@+id/tab_layout"
              android:layout_width="wrap_content"
              android:layout_height="match_parent"
              android:layout_centerHorizontal="true"
              app:tabBackground="@null"
              app:tabIndicatorColor="@color/tab_text_selected_color"
              app:tabIndicatorHeight="2dip"
              app:tabMode="fixed"
              app:tabGravity="fill"
              app:tabPaddingStart="14dp"
              app:tabPaddingEnd="14dp"
              app:tabTextAppearance="@style/FinanceTabTextAppearance"
              app:tabSelectedTextColor="@color/tab_text_selected_color"
              app:tabTextColor="@color/tab_text_unselected_color" />
       </android.support.v7.widget.Toolbar>
   </android.support.design.widget.AppBarLayout>
   <android.support.v4.view.ViewPager
       android:id="@+id/pager"
       android:layout_width="match_parent"
       android:paddingBottom="50dp"
       android:layout_height="match_parent"
       android:overScrollMode="never"
       app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
复制代码

布局是在Toolbar中添加一个TabLayout做为一级导航的tab。而后使用一个ViewPager,给该ViewPager添加了三个Fragment,分别是头条、产品和发现的Fragment。其中,头条Fragment中又嵌套了TabLayout和ViewPager。 基于沉浸式的实现方案,在代码中给AppBarLayout添加一个状态栏高度的padding。本觉得能够大功告成了,结果发现运行以后,在上滑隐藏AppBarLayout以后再下拉,会超出下拉范围,也就是下拉的时候会多出一条状态栏高度的空白,效果以下图顶部:

理财头条滑动问题

通过不断尝试和探索,发现给Activity添加以下flag便可:

this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
复制代码

嗯,不错,滑动问题解决了。但内心老是不安,总感受有坑。后面发现确实有坑,添加了这个flag后,部分带虚拟按键的华为手机出现虚拟按键挡住底部布局的问题,经验证只有EMUI3.1才有这个问题(又是EMUI3.1,已无力吐槽)。 最后百般周折,终于找到有效解决CoordinatorLayout+AppBarLayout并给AppBarLayout设置paddingtop以后的滑动问题的方法了。

this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
复制代码

本觉得上面解决方案已经完美没有任何问题了,没想到仍是有坑。不久后测试发现一个现网问题:当WebView中的输入框获取焦点软键盘弹出后,退出界面时底部布局出现软键盘大小的黑块。以下图所示:

附件1

经排查,此问题就是因为上面那段代码引发的。 没办法,只能去掉上面那段代码,寻找另外的解决方案来处理CoordinatorLayout+AppBarLayout并给AppBarLayout设置paddingtop的滑动问题了。 后来在发如今Activity的onCreate方法中加上下面一段代码就能够完美解决这个问题。

if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root_container_layout), new android.support.v4.view.OnApplyWindowInsetsListener() {
        @Override
       public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
            return insets.consumeSystemWindowInsets();
       }
    });
}
复制代码

4-总结

上面就是随手记Android项目中沉浸式状态栏实现过程当中遇到的坑以及解决方案。最终随手记Android实现状态栏效果后在不一样机型上面效果图以下:

4.2-6.0对比图

通过沉浸式状态栏的开发,发现几个容易踩的坑须要注意: 1.fitsSystemWindows=true要慎用,不少坑。好比WebView中输入框获取焦点弹出软键盘时出现抖动,还有哪一个View设置了fitsSystemWindows=true软键盘弹出时哪一个View就会被顶上去; 2.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS不要用,会致使EMUI3.1的系统下面虚拟按键挡住布局;

5-参考文档

stackoverflow.com/questions/7…

相关文章
相关标签/搜索