Android沉浸式状态栏小结

一、设置状态栏的颜色

Android 4.4系统及其以上的系统才能生效。

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" 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>

</resources>
关于每个属性的意思看下图就可以知道了。



二、设置状态栏为透明状态
windowTranslucentStatus可以设置状态栏为透明状态,但是它只能使用在Android 4.4(API 19)及其以上的系统上。

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>
需要说明的是,使用上面属性,Android 4.4 和 Android 5.0及其以后的系统是有区别的,Android 4.4系统效果为透明状态,5.0及其以后系统效果为半透明状态。

Android 4.4系统效果为透明状态



5.0及其以后系统效果为半透明状态


我们一般是设置Theme为Theme.AppCompat.Light.NoActionBar,然后自己定义一个ToolBar,当我们设置windowTranslucentStatus为true的时候,最终得到的效果如下:

可以看到,布局是从状态栏开始的,ToolBar移到了状态栏中,处理方法有:
1、得到状态栏的高度,然后设置ToolBar的Padding为状态栏的高度。

// A method to find height of the status bar
public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_drawer);

   // Retrieve the AppCompact Toolbar
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

   // Set the padding to match the Status Bar height
    toolbar.setPadding(0, getStatusBarHeight(), 0, 0);
}

效果如下:



2、给ToolBar添加android:fitsSystemWindows="true"

<android.support.v7.widget.Toolbar
            android:id="@+id/id_toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:fitsSystemWindows="true"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
android:fitsSystemWindows这个属性,主要是通过调整当前设置这个属性的view的padding去为我们的status_bar留下空间。

FitSystemWindow的原理

Android4.4与Android5.0的Insets处理机制完全不同。


Android 5.0的机制:

private void performTraversals() {
    ……
    dispatchApplyInsets(host);
    ……
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ……
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ……
    performDraw();
}
 
void dispatchApplyInsets(View host) {
    host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
}

SystemBar的尺寸在WindowInsets 中表示出来,比如Insets:(0, 63 , 0, 126)。表示StatusBar高度63,NavigationBar高度126.
dispatchApplyWindowInsets 将WindowInsets 从View树顶部开始分发。


首先我们来看看ViewGroup.java

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    // 1、执行父类的dispatchApplyWindowInsets方法
    // 其实就是执行View的dispatchApplyWindowInsets方法
    // 目的就是看是否需要自己消费掉Insets
    insets = super.dispatchApplyWindowInsets(insets);
    // 2、如果自己不处理,那么就处理子View给它的子View处理
    if (!insets.isConsumed()) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            insets = getChildAt(i).dispatchApplyWindowInsets(insets);
            if (insets.isConsumed()) {
                break;
            }
        }
    }
    return insets;
}

再来看看View的dispatchApplyWindowInsets方法

public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
        return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
    } else {
        return onApplyWindowInsets(insets);
    }
}

可以看到分为两步

1、如果设置View监听

View接收到Insets。会先判断自己是否被注册了监听,监听是指这个,在这个监听里能够收到Insets。并依据自己情况处理。

设置监听的方法

public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
    getListenerInfo().mOnApplyWindowInsetsListener = listener;
}
也就是说,我们可以设置监听自己来处理


2、执行onApplyWindowInsets方法

public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    if (fitSystemWindowsInt(insets.getSystemWindowInsets())) {
        //如果fitSystemWindowsInt返回true就消耗Instes,好简单的逻辑
        return insets.consumeSystemWindowInsets();
    }
    return insets;
}

重点来了fitSystemWindowsInt.它实质性的判断并设置了Padding。

private boolean fitSystemWindowsInt(Rect insets) {
    //如果设置了FITS_SYSTEM_WINDOWS这个flag
    if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
        mUserPaddingStart = UNDEFINED_PADDING;
        mUserPaddingEnd = UNDEFINED_PADDING;
        Rect localInsets = sThreadLocal.get();
        if (localInsets == null) {
            localInsets = new Rect();
            sThreadLocal.set(localInsets);
        }
 
        //computeFitSystemWindows主要就是localInsets=insets。并清空insets
        boolean res = computeFitSystemWindows(insets, localInsets);
 
        mUserPaddingLeftInitial = localInsets.left;
        mUserPaddingRightInitial = localInsets.right;
 
        //直接应用这个Insets到padding
        internalSetPadding(localInsets.left, localInsets.top,
                localInsets.right, localInsets.bottom);
        return res;
    }
    return false;
}

而FITS_SYSTEM_WINDOWS这个flag有2个来源:
1、代码手动设置:

public void setFitsSystemWindows(boolean fitSystemWindows) {
    setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
}

2、在XML中设置android:fitSystemWindow="true":

case com.android.internal.R.styleable.View_fitsSystemWindows:
     if (a.getBoolean(attr, false)) {
         viewFlagValues |= FITS_SYSTEM_WINDOWS;
         viewFlagMasks |= FITS_SYSTEM_WINDOWS;
     }
     break;

设置了fitSystemWindow,默认就会消费掉Insets,并设置padding。

这里我们就可以知道,其实对某个View设置了fitSystemWindow,本质就是为它设置了一个padding。


Android 4.4的机制:


在4.4中,直接设置padding,逻辑没有5.0那么复杂

private void performTraversals() {
    ……
    host.fitSystemWindows(mFitSystemWindowsInsets);
    ……
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ……
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ……
    performDraw();
}

让DecorView执行fitSystemWindows


首先看看ViewGroup.java

@Override
protected boolean fitSystemWindows(Rect insets) {
    // 1、执行父类的fitSystemWindows
    // 实质就是执行View的fitSystemWindows,看自己是否需要处理掉insets
    boolean done = super.fitSystemWindows(insets);
    // 2、如果自己不处理,就遍历它的孩子给子View处理
    if (!done) {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            done = children[i].fitSystemWindows(insets);
            if (done) {
                break;
            }
        }
    }
    return done;
}

下面看看View.java

protected boolean fitSystemWindows(Rect insets) {
    if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
        mUserPaddingStart = UNDEFINED_PADDING;
        mUserPaddingEnd = UNDEFINED_PADDING;
        Rect localInsets = sThreadLocal.get();
        if (localInsets == null) {
            localInsets = new Rect();
            sThreadLocal.set(localInsets);
        }
        boolean res = computeFitSystemWindows(insets, localInsets);
        mUserPaddingLeftInitial = localInsets.left;
        mUserPaddingRightInitial = localInsets.right;
        internalSetPadding(localInsets.left, localInsets.top,
                localInsets.right, localInsets.bottom);
        return res;
    }
    return false;
}

可以看到,跟5.0一样,看是否设置了fitSystemWindow属性,如果设置了,就设置一个padding。


3、使用开源库SystemBarTint
它可以设置状态栏的颜色和透明度

// create our manager instance after the content view is set
SystemBarTintManager tintManager = new SystemBarTintManager(this);
// enable status bar tint
tintManager.setStatusBarTintEnabled(true);
// enable navigation bar tint
tintManager.setNavigationBarTintEnabled(true);
// set the transparent color of the status bar, 20% darker
tintManager.setTintColor(Color.parseColor("#20000000"));



基于以上,我们一般会定义两套样式

1、在values文件夹中的styles.xml

<resources>
    <style name="BaseAppTheme" 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>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="@style/BaseAppTheme">
    </style>
</resources>
在Android 4.4以下系统中,它没有任何效果。

2、在values-v19文件夹中的样式styles.xml

<resources>
    <style name="AppTheme" parent="@style/BaseAppTheme">
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>

因为windowTranslucentStatus只能用在Android 4.4 及其以上的系统中。


补充:

StatusBar:



NavigationBar:



android从4.4开始,开始支持UI使用StatusBar与NavigationBar的范围。

所以要进行下面的配置:

在value中的styles.xml中设置

<!-- Base application theme. -->
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
</style>
<style name="AppTheme" parent="AppTheme.Base"></style>

在value-v19中的styles.xml中设置(为了兼容4.4)

<style name="AppTheme" parent="AppTheme.Base">
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
</style>

在value-v21中的styles.xml中设置

<style name="AppTheme" parent="AppTheme.Base">
    <!--透明状态栏-->
    <item name="android:windowTranslucentStatus">true</item>
    <!--透明导航栏-->
    <item name="android:windowTranslucentNavigation">true</item> 
    <!--使状态栏,导航栏可绘制-->
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
</style>




参考文章:Android and the transparent status bar

Android 沉浸式 UI 实现及原理

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

欢迎关注我的公众号:DroidMind

精品内容,独家发布


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值