Android 沉浸状态栏

前言

原文:http://blog.csdn.net/mybeta/article/details/50760323

这里说的沉浸状态栏是指透明状态栏,至于为什么国内喜欢将透明状态栏说成沉浸式状态栏,可参考 为什么在国内会有很多用户把「透明栏」(Translucent Bars)称作 「沉浸式顶栏」?

有很多其他地方都介绍了沉浸状态栏,可参考:

Android App 沉浸式状态栏解决方案

Android 沉浸式状态栏攻略 让你的状态栏变色吧

开源项目:SystemBarTint

本文主要介绍沉浸状态栏在4.4和5.x上的实现方式,以及搭配DrawerLayout和滑动返回的使用方法。

FLAG_TRANSLUCENT_STATUS

为什么4.4以上能设置沉浸状态栏,正是因为下面的flag引入了,通过这两个flag可以设置Status Bar或Nav Bar透明。

WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUSAdded in API level 19,如果这个flag被设置,View.SYSTEM_UI_FLAG_LAYOUT_STABLEView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 这两个flag会被自动添加到system UI visibility中。

如果要设置Nav Bar透明,需要添加下面的flag:

WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIONAdded in API level 19,如果这个flag被设置,View.SYSTEM_UI_FLAG_LAYOUT_STABLEView.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 这两个flag会被自动添加到system UI visibility中。

我们注意到上面的flag名为TRANSLUCENT,而不是TRANSPARENT知乎 指出了,TranslucentStatus 在 4.4 和 5.x 上表现不同,4.4 是一层渐变的遮罩层,5.x 以上是一条半透明的遮罩层,比如Genymotion模拟器上就是这样,但在一些其他机器比如小米上,就是全透明的,这应该是和系统有关的。

Genymotion模拟器4.4上透明状态栏的效果:

小米4.4上透明状态栏的效果:

Genymotion模拟器5.x上透明状态栏的效果:

4.4上的解决方案

在5.x以上可以通过现有API直接设置StatusBar的颜色,可以满足大部分的需求,那如何兼容4.4呢?

目前在4.4上StatusBar变色的基本原理就是将StatusBar本身设置为透明,然后在StatusBar的位置添加一个相同大小的View并着色。可以参考 SystemBarTint。具体实现如下:

1、设置状态栏透明

final int sdk = Build.VERSION.SDK_INT;
Window window = mActivity.getWindow();
WindowManager.LayoutParams params = window.getAttributes();

if (sdk >= Build.VERSION_CODES.KITKAT) {
    int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
    // 设置透明状态栏
    if ((params.flags & bits) == 0) {
        params.flags |= bits;
        window.setAttributes(params);
    }
}

2、设置fitsSystemWindows

当设置Status Bar透明后,由于visibility flag的自动添加,屏幕会变成全屏的。所以这时候ToolBar就直接跑到StatusBar下面了,如图:

这时需要设置设置Activity的layout的属性android:fitsSystemWindows="true",添加后的效果:

3、状态栏着色

状态栏的位置已经空出来了,接下来添加一个View,这个View是添加在layout hierarchy里的。

final int sdk = Build.VERSION.SDK_INT;
Window window = mActivity.getWindow();
WindowManager.LayoutParams params = window.getAttributes();

if (sdk == Build.VERSION_CODES.KITKAT) {
    int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
    // 设置透明状态栏
    if ((params.flags & bits) == 0) {
        params.flags |= bits;
        window.setAttributes(params);
    }

    // 设置状态栏颜色
    ViewGroup contentLayout = (ViewGroup) mActivity.findViewById(android.R.id.content);
    setupStatusBarView(contentLayout, color);

    // 设置Activity layout的fitsSystemWindows
    View contentChild = contentLayout.getChildAt(0);
    contentChild.setFitsSystemWindows(true);
}
private void setupStatusBarView(ViewGroup contentLayout, int color) {
    if (mStatusBarView == null) {
        View statusBarView = new View(mActivity);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(mActivity));
        contentLayout.addView(statusBarView, lp);

        mStatusBarView = statusBarView;
    }

    mStatusBarView.setBackgroundColor(color);
}

/**
 * 获得状态栏高度
 */
private static int getStatusBarHeight(Context context) {
    int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
    return context.getResources().getDimensionPixelSize(resourceId);
}

效果如下:

这时候通过hierarchyviewer查看布局:

可以看到android.R.id.content下除了Activity的layout外还多了一个View,所以我们也可以将这个View添加到Activity的layout下,甚至可以给ToolBar设置paddingTop,值为状态栏高度。但对大量页面来说,显然上面的方法更好。

5.x以上的解决方案

在5.x上,完全可以用上面4.4的方法设置状态栏颜色,在Genymotion模拟器5.x上的效果:

这里就出现了上面说的flag的问题,在部分机型上Status Bar不能设置为全透明。

如果用5.x自带的方法window.setStatusBarColor(getResources().getColor(R.color.colorPrimary))设置,效果如下:

这里没有设置Status Bar透明,所以页面不是全屏的,所以也不用设置fitSystemWindows。一般来说,5.x以上用window.setStatusBarColor()就能满足需求了。

特殊情况下的解决方案

在一些情况下,页面内容需要全屏显示,如下图:

<img src="http://upload-images.jianshu.io/upload_images/1314570-d6b96615f8aadaf0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="384" height="640" />

在4.4上,设置状态栏透明后,页面已经是全屏的,这时候只要设置状态栏的View的背景为透明即可。

在5.x上,需要先设置页面全屏,用4.4上使用的flag可以实现,但可能在不同机型上有不同的效果,这里用全屏+自带API的方法来实现。可参考:Android 5.0 如何实现将布局的内容延伸到状态栏实?

1、实现全屏:

window.getDecorView().setSystemUiVisibility(
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

2、设置Status Bar颜色:

window.setStatusBarColor(color);

3、要注意的是,由于页面是全屏的,所以Activity的layout不能设置android:fitSystemWindows="true",但又不能让ToolBar显示在Status Bar下面,这里有很多种方法,可以设置ToolBar的paddingTop,也可以使用下面的方法,给ToolBar套一层layout,然后设置它的android:fitsSystemWindows="true"

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
</FrameLayout>

输入法的问题

WindowManager.LayoutParams.FLAG_FULLSCREEN 中提到过,A fullscreen window will ignore a value of SOFT_INPUT_ADJUST_RESIZE for the window's softInputMode field; the window will stay fullscreen and will not resize.,全屏模式下,SOFT_INPUT_ADJUST_RESIZE 会失效。

解决方法:

1、stackoverflow 上指出,对Activity的根布局设置android:fitsSystemWindows="true"就能解决问题。

2、但在一些页面内容需要全屏显示的情况下,就不能这么设置了。 AndroidBug5497Workaround 提到了另一种方法,经测试,如果Activity layout中的所有控件都设置android:fitsSystemWindows="true",是有效的,但如果对其中的部分控件设置了android:fitsSystemWindows="true",该方法就失效了。而且该方法中计算的高度数据并不是很准确,所以暂不推荐使用该方法。如果要使用该方案,在 Android 5.0 如何实现将布局的内容延伸到状态栏实? - 张启的回答 中提到了,建议只设置 windowTranslucentStatus,不要同时设置 Navigation,因为一旦同时启用了这两个属性,那么该解决方案就无效了。

3、既然对Activity的根布局设置android:fitsSystemWindows="true"就能解决问题,那就从这里入手,CustomInsetsFrameLayout提到了一种方法,仍然是给Activity的根布局设置android:fitsSystemWindows="true",但在将insets转换成padding的时候,将padding值设置为0,以解决全屏和fitSystemWindows共存的问题。这个类中也指出了,不能将insets.bottom设置为0,否则该方法会失效。

DrawerLayout解决方案

根据 Navigation drawer 的效果,DrawerLayout展开的时候侧边栏看起来像是在Status Bar下面。在5.x以上,DrawerLayout本身就能实现该效果。

5.x效果实现

5.x上的效果实现可以参考 Material Design 之 侧边栏与 Status Bar 不得不说的故事

如果想要侧边栏能显示在Status Bar下面,要满足两个条件:1、页面要全屏;2、Status Bar的颜色要透明。

与此同时,也需要解决下面两个问题:

1、页面全屏后如何处理ToolBar等内容的位置。

2、Status Bar透明了,如何让Status Bar看起来像正常设置了颜色一样。

在5.x上,DrawerLayout本身已经满足了上面两个条件,并且已经处理了上面两个问题。(仅仅在5.x上实现了)

分析源码,DrawerLayout构造方法中有这样一段代码:

public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
    // ...
    if (ViewCompat.getFitsSystemWindows(this)) {
        IMPL.configureApplyInsets(this);
        mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
    }
    // ...
}

这里的IMPL只对5.x以上做了具体实现,IMPL.configureApplyInsets(this)在5.x上的实现如下:

public void configureApplyInsets(View drawerLayout) {
    DrawerLayoutCompatApi21.configureApplyInsets(drawerLayout);
}

class DrawerLayoutCompatApi21 {
    // 省略...

    public static void configureApplyInsets(View drawerLayout) {
        if (drawerLayout instanceof DrawerLayoutImpl) {
            drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener());
            drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
        }
    }

    // 省略...
}

在这里添加了View.SYSTEM_UI_FLAG_LAYOUT_STABLEView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,这时页面已经是全屏的了,满足了上面的条件一:1、页面要全屏。条件二需要在Activity中设置getWindow().setStatusBarColor(Color.TRANSPARENT)或是在theme中设置。这里还设置了setOnApplyWindowInsetsListener()方法,用来处理fitSystemWindows()相关方法的逻辑。InsetsListener的实现如下:

static class InsetsListener implements View.OnApplyWindowInsetsListener {
    @Override
    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
        final DrawerLayoutImpl drawerLayout = (DrawerLayoutImpl) v;
        drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0);
        return insets.consumeSystemWindowInsets();
    }
}

直接调用到了DrawerLayout中的方法:

public void setChildInsets(Object insets, boolean draw) {
    mLastInsets = insets;
    mDrawStatusBarBackground = draw;
    setWillNotDraw(!draw && getBackground() == null);
    requestLayout();
}

页面重新layout,看到onMeasure()方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // ...

    final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
    final int layoutDirection = ViewCompat.getLayoutDirection(this);

    // Gravity value for each drawer we've seen. Only one of each permitted.
    int foundDrawers = 0;
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);

        if (child.getVisibility() == GONE) {
            continue;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (applyInsets) {
            final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection);
            if (ViewCompat.getFitsSystemWindows(child)) {
                IMPL.dispatchChildInsets(child, mLastInsets, cgrav);
            } else {
                IMPL.applyMarginInsets(lp, mLastInsets, cgrav);
            }
        }

        // ...
    }
}

根据上面的分析,这里applyInsets仅当DrawerLayout设置了android:fitsSystemWindows="true"并且是5.x以上的系统才会为true。

进入for循环,对DrawerLayout的child进行遍历,这里需要分析一下里面的if语句。

IMPL.dispatchChildInsets(child, mLastInsets, cgrav) 代表的是如果child设置了 android:fitsSystemWindows="true",那么对这个child进行fitSystemWindows操作,即insets的值会添加到child的padding里面。注意:上面的 InsetsListener 中,返回的结果是消费了fitsSystemWindows事件的,按正常逻辑是如果child也设置了 android:fitsSystemWindows="true" 是没有效果的,而这里DrawerLayout主动对child进行了该处理,所以在DrawerLayout中,同时给它本身和它的child设置 android:fitsSystemWindows="true",都是有效果的。另外,InsetsListener没有insets值添加到DrawerLayout本身的padding里面。

如果child没有设置 android:fitsSystemWindows="true",则会调用 IMPL.applyMarginInsets(lp, mLastInsets, cgrav),该方法在5.x上的实现是将insets的值设置为child.LayoutParams的margin值。

然后看onLayout()方法:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mInLayout = true;
    final int width = r - l;
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);

        if (child.getVisibility() == GONE) {
            continue;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (isContentView(child)) {
            child.layout(lp.leftMargin, lp.topMargin,
                    lp.leftMargin + child.getMeasuredWidth(),
                    lp.topMargin + child.getMeasuredHeight());
        } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
        }

        // ...
    }   
}

看到if (isContentView(child)),这里对ContentView进行child.layout()操作,只考虑了child的margin值,而没有考虑DrawerLayout本身的padding值,所以即使给DrawerLayout设置padding,也不会对child的位置有影响,在4.4上同样如此。而根据上面onMeasure()的分析,insets的值会被添加到child的padding或者margin里面,所以这里layout的时候,child的内容是不会跑到Status Bar下面去的。

到这里已经解决了上面的问题一:1、页面全屏后如何处理ToolBar等内容的位置。

这时整个DrawerLayout都已经按正常位置layout完成了,只剩下Status Bar下的空白区域了,我们要做的就是对这块区域着色。接下来看onDraw()方法:

public void onDraw(Canvas c) {
    super.onDraw(c);
    if (mDrawStatusBarBackground && mStatusBarBackground != null) {
        final int inset = IMPL.getTopInset(mLastInsets);
        if (inset > 0) {
            mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
            mStatusBarBackground.draw(c);
        }
    }
}

这里的作用是在Status Bar的位置画一块颜色区域,因为inset只在5.x上才会有值,所以这里也只针对5.x进行了处理。这里就解决了上面的问题二:2、Status Bar透明了,如何让Status Bar看起来像正常设置了颜色一样

最后就是如何让Drawer弹出来的时候看起来像是在Status Bar下面,这里用到了这个类 ScrimInsetsFrameLayout新版),作用就是在Status Bar的位置画一层半透明的颜色,从而实现效果。具体的可参考 How do I use DrawerLayout to display over the ActionBar/Toolbar and under the status bar?

最终效果如下:

<img src="http://upload-images.jianshu.io/upload_images/1314570-6051f9150f33ee8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="384" height="640" />

4.4效果实现

根据上面的分析,DrawerLayout在4.4上没有对状态栏做任何处理,所以得我们自己来实现。按下面步骤来分析:

1、和5.x中一样,需要设置Status Bar透明,否则侧边栏会被遮住。

2、因为要让侧边栏全屏显示,所以不能对DrawerLayout设置android:fitsSystemWindows="true"

3、5.x上DrawerLayout本身已有关于Status Bar的实现,所以这里不能影响5.x。

实现这三点的代码如下:

StatusBarHelper helper = new StatusBarHelper(this, StatusBarHelper.LEVEL_19_TRANSLUCENT,
                    StatusBarHelper.LEVEL_NONE);
helper.setActivityRootLayoutFitSystemWindows(false);
helper.setColor(Color.TRANSPARENT);

4、如何设置Status Bar位置的颜色呢?可以给ContentView设置一个padding或者margin(5.x中就是这么做的),这样Status Bar的位置就空出来了,那如何着色呢(DrawerLayout在4.4不支持着色,需要自己实现)?这里有多种方法,如果有ToolBar,可以给ToolBar设置一个paddingTop,然后Status Bar的位置就和ToolBar一样的颜色了。或者也可以在ToolBar上面加一个View,这样就可以自定义颜色,但要注意只能在4.4上显示出来。这里提供一种更加优雅的方法,对 ScrimInsetsFrameLayout新版)进行修改:

public class ScrimInsetsFrameLayout extends FrameLayout {
    private Drawable mInsetForeground;
    private boolean mConsumeInsets;

    private Rect mInsets;
    private Rect mTempRect = new Rect();
    private OnInsetsCallback mOnInsetsCallback;

    public ScrimInsetsFrameLayout(Context context) {
        super(context);
        init(context, null, 0);
    }

    public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs, defStyle);
    }

    private void init(Context context, AttributeSet attrs, int defStyle) {
        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ScrimInsetsView, defStyle, 0);
        if (a == null) {
            return;
        }
        mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsView_appInsetForeground);
        mConsumeInsets = a.getBoolean(R.styleable.ScrimInsetsView_appConsumeInsets, true);
        a.recycle();

        setWillNotDraw(true);
    }

    @Override
    protected boolean fitSystemWindows(Rect insets) {
        mInsets = new Rect(insets);
        setWillNotDraw(mInsetForeground == null);
        ViewCompat.postInvalidateOnAnimation(this);
        if (mOnInsetsCallback != null) {
            mOnInsetsCallback.onInsetsChanged(insets);
        }
        return mConsumeInsets || super.fitSystemWindows(insets);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        int width = getWidth();
        int height = getHeight();
        if (mInsets != null && mInsetForeground != null) {
            int sc = canvas.save();
            canvas.translate(getScrollX(), getScrollY());

            // Top
            mTempRect.set(0, 0, width, mInsets.top);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            // Bottom
            mTempRect.set(0, height - mInsets.bottom, width, height);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            // Left
            mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            // Right
            mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            canvas.restoreToCount(sc);
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mInsetForeground != null) {
            mInsetForeground.setCallback(this);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mInsetForeground != null) {
            mInsetForeground.setCallback(null);
        }
    }

    /**
     * Allows the calling container to specify a callback for custom processing when insets change (i.e. when
     * {@link #fitSystemWindows(Rect)} is called. This is useful for setting padding on UI elements based on
     * UI chrome insets (e.g. a Google Map or a ListView). When using with ListView or GridView, remember to set
     * clipToPadding to false.
     */
    public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) {
        mOnInsetsCallback = onInsetsCallback;
    }

    public interface OnInsetsCallback {
        void onInsetsChanged(Rect insets);
    }
}

然后我们的layout文件就可以这样写:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout android:id="@+id/drawer"
                                        xmlns:android="http://schemas.android.com/apk/res/android"
                                        xmlns:app="http://schemas.android.com/apk/res-auto"
                                        xmlns:tools="http://schemas.android.com/tools"
                                        android:layout_width="match_parent"
                                        android:layout_height="match_parent"
                                        android:fitsSystemWindows="true"
                                        tools:context=".MainActivity">

    <!--content-->
    <me.naturs.statusbarhelper.ScrimInsetsFrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:appConsumeInsets="false"
        app:appInsetForeground="@color/colorPrimaryDark">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@color/colorPrimary"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>

        </LinearLayout>
    </me.naturs.statusbarhelper.ScrimInsetsFrameLayout>

    <!--drawer-->
    <me.naturs.statusbarhelper.ScrimInsetsFrameLayout
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:appInsetForeground="#4000">
    </me.naturs.statusbarhelper.ScrimInsetsFrameLayout>

</android.support.v4.widget.DrawerLayout>

这里在原来的ScrimInsetsFrameLayout基础上添加一个appConsumeInsets属性,用来控制protected boolean fitSystemWindows(Rect insets)方法的返回值。上面content的layout设置为app:appConsumeInsets="false",但没有设置android:fitsSystemWindows="true",这样Status Bar区域着色了,但没有消耗insets事件,也就不会影响drawer的着色了。

最后需要在代码中监听insets的变化,以便给content设置padding:

mScrimInsetsFrameLayout = (ScrimInsetsFrameLayout) findViewById(R.id.layout);
mScrimInsetsFrameLayout.setOnInsetsCallback(new ScrimInsetsFrameLayout.OnInsetsCallback() {
    @Override
    public void onInsetsChanged(Rect insets) {
        mScrimInsetsFrameLayout.setPadding(0, insets.top, 0, 0);
    }
});

同样的代码运行在5.x上,不会有任何影响,不需要做额外的处理。因为5.x上会在DrawerLayout中设置ContentView的padding或margin,这里fitSystemWindows为false,所以会设置margin,也就是ScrimInsetsFrameLayout.fitSystemWindows()方法不会被调用。

搭配滑动返回使用

滑动返回的开源库:SwipeBackLayout

与滑动返回搭配使用的时候,需要考虑的问题主要是滑动返回的时候,状态栏该怎样变化,而该沉浸状态栏的实现方式可以直接兼容滑动返回。看到SwipeBackLayout中的关键代码:

public void attachToActivity(Activity activity) {
    mActivity = activity;
    TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{
            android.R.attr.windowBackground
    });
    int background = a.getResourceId(0, 0);
    a.recycle();

    ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
    ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
    decorChild.setBackgroundResource(background);
    decor.removeView(decorChild);
    addView(decorChild);
    setContentView(decorChild);
    decor.addView(this);
}

滑动返回的layout是直接添加到decor view下的,而我们的沉浸状态栏是在android.R.id.content下处理的。但要注意的是,在5.x上使用的时候,应该使用 StatusBarHelper.LEVEL_21_VIEW 才能达到更好的效果。

开源

项目地址:https://github.com/naturs/StatusBarHelper

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容