关于沉浸式状态栏的学习

345 阅读2分钟

关于沉浸式状态栏我们首先都要设置为全屏和透明导航栏。

<!--状态栏设置为透明背景-->
<item name="colorPrimary">@color/translate</item>
<!--设置全屏-->
<item name="android:windowFullscreen">true</item>

我们都知道当设置为全屏模式的时候,我们的视图会跑到状态栏的后面。所以在做沉浸式状态栏的时候需要解决的就是被状态栏遮盖的问题。
那我们思考下这个问题如何解决~
*
*
*
*
*
*
思考结束,结果就是,无外乎就是计算状态栏的高度,然后给被覆盖的view一个marginTop或者给父视图一个paddingTop。我知道说到现在会有很多人觉得这个方式太粗暴了不优雅,那现在给你看看官方是如何实现的,如何让你的代码变得优雅起来。

官方给我们提供了一个属性:android:fitsSystemWindows="true",当在布局的最顶层视图设置了这个属性,我们的遮盖问题就解决了。现在我先不说他的缺点,我先给你分析下他的具体实现:
首先看这个View有没有设置属性fitsSystemWindows,设置有的话给viewFlagValues设置FITS_SYSTEM_WINDOWS标志位。

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

View的onApplyWindowInsets方法会被调用,WindowInsets这个类里面保存了整个视图窗口被系统视图占据的位置数据,我们也就是根据这个类里面提供的数据来知道状态栏的高度的。

public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
            && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
        return onApplyFrameworkOptionalFitSystemWindows(insets);
    }
    if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
        if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
            return insets.consumeSystemWindowInsets();
        }
    } else {
        // We were called from within a direct call to fitSystemWindows.
        if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
            return insets.consumeSystemWindowInsets();
        }
    }
    return insets;
}

private boolean fitSystemWindowsInt(Rect insets) {
    if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
        Rect localInsets = sThreadLocal.get();
        boolean res = computeFitSystemWindows(insets, localInsets);
        applyInsets(localInsets);
        return res;
    }
    return false;
}

private void applyInsets(Rect insets) {
    mUserPaddingStart = UNDEFINED_PADDING;
    mUserPaddingEnd = UNDEFINED_PADDING;
    mUserPaddingLeftInitial = insets.left;
    mUserPaddingRightInitial = insets.right;
    internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
}


protected void internalSetPadding(int left, int top, int right, int bottom) {
    final int viewFlags = mViewFlags;
    boolean changed = false;
    if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
    if (mPaddingLeft != left) {
        changed = true;
        mPaddingLeft = left;
    }
    if (mPaddingTop != top) {
        changed = true;
        mPaddingTop = top;
    }
    if (mPaddingRight != right) {
        changed = true;
        mPaddingRight = right;
    }
    if (mPaddingBottom != bottom) {
        changed = true;
        mPaddingBottom = bottom;
    }

    if (changed) {
        requestLayout();
        invalidateOutline();
    }
}

可以看到通过这一系列走下就是给我们的父视图设置了mPaddingTop从而实现了视图不被遮挡的问题。

现在介绍一个更加灵活的方式WindowInsetsController,这个类是谷哥官方提供给咱们的,使用方式如下:

 ViewCompat.setOnApplyWindowInsetsListener(button) { view, insets ->
     val params = view.layoutParams as FrameLayout.LayoutParams
     params.topMargin = insets.systemWindowInsetTop
     insets
 }

这就很优雅灵活了。 参考链接

通过这次的学习,我发现了几个官方提供的比较有意思的新类:

  1. WindowInsetsController比如你可以用他控制软件盘的展示和隐藏等等可以看下这个,参2
  2. WindowInsetsControllerCompat
  3. setSystemUiVisibility详解