沃梦达 / IT编程 / 移动开发 / 正文

Android 超详细讲解fitsSystemWindows属性的使用

fitsSystemWindows属性可以让view根据系统窗口来调整自己的布局;简单点说就是我们在设置应用布局时是否考虑系统窗口布局,这里系统窗口包括系统状态栏、导航栏、输入法等,包括一些手机系统带有的底部虚拟按键

对于android:fitsSystemWindows这个属性你是否感觉又熟悉又陌生呢?

熟悉是因为大概知道它可以用来实现沉浸式状态栏的效果,陌生是因为对它好像又不够了解,这个属性经常时灵时不灵的。

其实对于android:fitsSystemWindows属性我也是一知半解,包括我在写《第一行代码》的时候对这部分知识的讲解也算不上精准。但是由于当时的理解对于我来说已经够用了,所以也就没再花时间继续深入研究。

而最近因为工作的原因,我又碰上了android:fitsSystemWindows这个属性,并且我之前的那些知识储备已经不够用了。所以这次趁着这个机会,我把这部分知识又重新学习了一遍,并整理成一篇文章分享给大家。

我们都不会无缘无故去接触一个属性。我相信用到android:fitsSystemWindows的朋友基本都是为了去实现沉浸式状态栏效果的。

这里我先解释一下什么是沉浸式状态栏效果。

Android手机顶部用于显示各种通知和状态信息的这个栏叫做状态栏。


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        window.statusBarColor = Color.TRANSPARENT
    }
    
}

接下来,我们给activity_main.xml的根布局加上android:fitsSystemWindows属性,并且给该布局设置了一个背景色用于观察效果:


<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff66ff"
    android:fitsSystemWindows="true">

</FrameLayout>

运行一下代码,效果如下图所示:


<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff66ff"
    android:fitsSystemWindows="true">

</androidx.coordinatorlayout.widget.CoordinatorLayout>

可以看到,这里只是将根布局从FrameLayout修改成了CoordinatorLayout,其他都没有任何变化。然后重新运行程序。效果如下图所示:


private void setupForInsets() {
    if (Build.VERSION.SDK_INT < 21) {
        return;
    }

    if (ViewCompat.getFitsSystemWindows(this)) {
        if (mApplyWindowInsetsListener == null) {
            mApplyWindowInsetsListener =
                    new androidx.core.view.OnApplyWindowInsetsListener() {
                        @Override
                        public WindowInsetsCompat onApplyWindowInsets(View v,
                                WindowInsetsCompat insets) {
                            return setWindowInsets(insets);
                        }
                    };
        }
        // First apply the insets listener
        ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);

        // Now set the sys ui flags to enable us to lay out in the window insets
        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    } else {
        ViewCompat.setOnApplyWindowInsetsListener(this, null);
    }
}

可以看到,这里当发现CoordinatorLayout设置了android:fitsSystemWindows属性时,会对当前布局的insets做一些处理,并且调用了下面一行代码:


setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

这行代码是一切的关键所在。准确来讲,就是因为执行了这行代码,我们才能将布局的内容延伸到系统状态栏区域。

是不是感觉解密了?但事实上CoordinatorLayout所做的事情还远不止这些。

因为沉浸式状态栏其实会带来很多问题。让布局的内容延伸到状态栏的背后,如果一些可交互的控件被状态栏遮挡了怎么办?这样这些控件可能就无法点击和交互了。

CoordinatorLayout为了解决这个问题,会对所有内部的子View都进行一定程度的偏移,保证它们不会被状态栏遮挡住。

比如我们在CoordinatorLayout当中再添加一个按钮:


<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff66ff"
    android:fitsSystemWindows="true">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

运行一下程序,效果如下图所示:


<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff66ff"
    android:fitsSystemWindows="true">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="QGRyYXdhYmxlL2Jn"
        />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

现在运行一下程序,效果如下图所示:


<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff66ff"
    android:fitsSystemWindows="true">

    <com.google.android.material.appbar.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="QGRyYXdhYmxlL2Jn"
            android:fitsSystemWindows="true"
            />

    </com.google.android.material.appbar.CollapsingToolbarLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

可以看到,这里我们在ImageView的外面又包裹了一层CollapsingToolbarLayout,并且给CollapsingToolbarLayout也设置了android:fitsSystemWindows属性,这样CollapsingToolbarLayout就可以将内容延申到状态栏区域了。

接着我们给ImageView同样设置了android:fitsSystemWindows属性,如此一来,就可以让图片显示在状态栏的背后了。

重新运行一下程序,效果如下图所示:


<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff66ff">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="QGRyYXdhYmxlL2Jn"
        />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        />

</FrameLayout>

为了实现沉浸式状态栏的效果,我们手动在MainActivity当中调用setSystemUiVisibility()函数,来将FrameLayout的内容延伸到状态栏区域:


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        window.statusBarColor = Color.TRANSPARENT
        val frameLayout = findViewById<FrameLayout>(R.id.root_layout)
        frameLayout.systemUiVisibility = (SYSTEM_UI_FLAG_LAYOUT_STABLE
                or SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
    }

}

这里提醒一点,setSystemUiVisibility()函数其实已经被废弃了。从Android 11开始,Google提供了一个新的API WindowInsetsController来实现同样的功能,不过本篇文章就不往这方面展开了。

现在重新运行一下程序,效果如下图所示:


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        window.statusBarColor = Color.TRANSPARENT
        val frameLayout = findViewById<FrameLayout>(R.id.root_layout)
        frameLayout.systemUiVisibility = (SYSTEM_UI_FLAG_LAYOUT_STABLE
                or SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

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

}

可以看到,当监听到WindowInsets发生变化时,我们调用systemWindowInsetTop即可获取到状态栏的高度,然后对不需要延伸到状态栏区域的控件进行相应的偏移即可。

重新运行程序,效果如下图所示:

好了,到这里为止,我们就将实现沉浸式状态栏背后的原理,以及具体的多种实现方式都介绍完了。

这次你学懂android:fitsSystemWindows属性了吗?

到此这篇关于Android 超详细讲解fitsSystemWindows属性的使用的文章就介绍到这了,更多相关Android fitsSystemWindows属性内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!

本文标题为:Android 超详细讲解fitsSystemWindows属性的使用