Android 通过DecorView计算statusBar、navigationBar的高度

背景

近期在做项目的时候碰到了底部虚拟按键在各个厂商适配的问题,闷逼了一圈,后面搜索一圈,发现即使各大厂商有变动,还是离不开原生本质

正题

我们都知道activity >> window >> decorView,适配的问题,闷逼了一圈,后面搜索一圈,发现即使各大厂商有变动,还是离不开原生本质

activity 的 decorview

我们都知道activity >> window >> decorView,Window是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow。

  • Activity相关代码:
代码语言:javascript
复制
public class Activity extends ContextThemeWrappe{
  private Window mWindow;

mWindow = new PhoneWindow(this);
}

  • PhoneWindow 相关代码:
代码语言:javascript
复制
public class PhoneWindow extends Window{
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
}

DocorView包含了一个状态栏,一个navigationBar,一个LinearLayout我们通常的内容展示区,如下:

代码语言:javascript
复制
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical">
<!-- Popout bar for action modes -->
<ViewStub
android:id="@+id/action_mode_bar_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:theme="?attr/actionBarTheme" />

&lt;FrameLayout
    style=&#34;?android:attr/windowTitleBackgroundStyle&#34;
    android:layout_width=&#34;match_parent&#34;
    android:layout_height=&#34;?android:attr/windowTitleSize&#34;&gt;

    &lt;TextView
        android:id=&#34;@android:id/title&#34;
        style=&#34;?android:attr/windowTitleStyle&#34;
        android:layout_width=&#34;match_parent&#34;
        android:layout_height=&#34;match_parent&#34;
        android:background=&#34;@null&#34;
        android:fadingEdge=&#34;horizontal&#34;
        android:gravity=&#34;center_vertical&#34; /&gt;
&lt;/FrameLayout&gt;

&lt;FrameLayout
    android:id=&#34;@android:id/content&#34;
    android:layout_width=&#34;match_parent&#34;
    android:layout_height=&#34;0dip&#34;
    android:layout_weight=&#34;1&#34;
    android:foreground=&#34;?android:attr/windowContentOverlay&#34;
    android:foregroundGravity=&#34;fill_horizontal|top&#34; /&gt;

</LinearLayout>

在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。相关源码扯犊子到这边差不多,可以知道statusbar和navigationBar两者和decorView的关系了,就是他的两个儿子。

计算statusBar和NavigationBar的高度
代码语言:javascript
复制
public class DecorUtil {

/**
 * 请勿在dialog中使用
 * &lt;p&gt;
 * 主题的 android:windowTranslucentStatus 属性, 会影响 contentView 的 padding top.
 * &lt;p&gt;
 * 如果设置了 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN , 那么 contentView 的 padding top 都是 0
 */
public static void demo(@NonNull final Window window) {
    final View decorView = window.getDecorView();
    int measuredHeight = decorView.getMeasuredHeight();
    if (measuredHeight &lt;= 0) {
        decorView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                decorView.getViewTreeObserver().removeOnPreDrawListener(this);

                demo(window);
                return true;
            }
        });
    } else {
        Rect outRect = new Rect();
        decorView.getWindowVisibleDisplayFrame(outRect);
        L.w(&#34;可视区域:&#34; + outRect);

        L.w(&#34;屏幕高度:&#34; + measuredHeight);
        if (decorView instanceof ViewGroup) {
            int childCount = ((ViewGroup) decorView).getChildCount();
            if (childCount &gt; 0) {
                View contentView = ((ViewGroup) decorView).getChildAt(0);
                L.w(&#34;内容高度:&#34; + contentView.getMeasuredHeight() + &#34; p:&#34; + contentView.getPaddingTop());
            }
            if (childCount &gt; 1) {
                View childView = ((ViewGroup) decorView).getChildAt(1);
                if (isStatusBar(decorView, childView)) {
                    L.w(&#34;状态栏高度:&#34; + childView.getMeasuredHeight());
                } else if (isNavigationBar(decorView, childView)) {
                    L.w(&#34;导航栏高度:&#34; + childView.getMeasuredHeight());
                } else {
                    L.w(&#34;未知:&#34; + childView);
                }
            }
            if (childCount &gt; 2) {
                View childView = ((ViewGroup) decorView).getChildAt(2);
                if (isStatusBar(decorView, childView)) {
                    L.w(&#34;状态栏高度:&#34; + childView.getMeasuredHeight());
                } else if (isNavigationBar(decorView, childView)) {
                    L.w(&#34;导航栏高度:&#34; + childView.getMeasuredHeight());
                } else {
                    L.w(&#34;未知:&#34; + childView);
                }
            }
        }
    }
}

private static boolean isStatusBar(@NonNull View decorView, @NonNull View childView) {
    if (childView.getTop() == 0 &amp;&amp;
            childView.getMeasuredWidth() == decorView.getMeasuredWidth() &amp;&amp;
            childView.getBottom() &lt; decorView.getBottom()
            ) {
        return true;
    }
    return false;
}

private static boolean isNavigationBar(@NonNull View decorView, @NonNull View childView) {
    if (childView.getTop() &gt; decorView.getTop() &amp;&amp;
            childView.getMeasuredWidth() == decorView.getMeasuredWidth() &amp;&amp;
            childView.getBottom() == decorView.getBottom()
            ) {
        return true;
    }
    return false;
}

}

参考文献

https://blog.csdn.net/angcyo/article/details/53240763