当前位置: 首页 > 科技观察

再学习下Android-fitsSystemWindows属性,你学会了吗?

时间:2023-03-14 20:50:10 科技观察

属性android:fitsSystemWindows是不是觉得熟悉又陌生?你很熟悉,因为你可能知道它可以用来实现沉浸式状态栏的效果。你不熟悉,是因为你对它的了解还不够。时机不佳。其实我对android:fitsSystemWindows这个属性也略有了解,包括我在写《第一行代码》的时候对这部分知识的解释。但由于当时的了解对我来说已经足够了,所以我没有再花时间进一步研究。最近因为工作原因,又碰到了android:fitsSystemWindows这个属性,之前的知识储备已经不够用了。所以这一次,借此机会,我重新学习了这部分知识,并整理成一篇文章与大家分享。我们都不会无缘无故地触摸财产。相信使用android:fitsSystemWindows的朋友基本都是为了实现沉浸式状态栏效果。这里我先解释一下什么是沉浸式状态栏效果。Android手机顶部用来显示各种通知和状态信息的栏称为状态栏。通常情况下,我们应用程序的内容显示在状态栏下方。但有时为了达到更好的视觉效果,我们想把应用的内容延伸到状态栏后面,可以称之为沉浸式状态栏。那么如何借助android:fitsSystemWindows属性实现沉浸式状态栏效果呢?为什么这个属性总是无效?接下来,我们就一步步揭开谜底。相信按照大多数人美好的想象,android:fitsSystemWindows属性应该就像一个开关一样。设置为true开启沉浸式状态栏效果,设置为false关闭沉浸式状态栏效果。但现实并非如此。让我们用一个代码示例来演示它。首先,为了验证沉浸式状态栏的效果,需要将系统的状态栏改为透明色。代码如下:classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)window.statusBarColor=Color.TRANSPARENT}}接下来,我们添加将android:fitsSystemWindows属性添加到activity_main.xml的根布局,并为布局设置一个背景色,观察效果:运行代码,效果如下图所示:从布局的背景颜色可以看出,布局不会扩展到系统状态栏后面。也就是说,即使设置了android:fitsSystemWindows属性,我们也没有实现沉浸式状态栏效果。不过不用担心,我们只需要做如下小修改:可以看到,根布局只是从FrameLayout变成了CoordinatorLayout,其他没有变化。然后再次运行该程序。效果如下图所示:这样就可以成功实现沉浸式状态栏效果了。也就是说,为什么android:fitsSystemWindows属性在CoordinatorLayout布局上设置时生效,而在FrameLayout布局上设置时却没有效果呢?这是因为xml中的配置毕竟只是一个标记。如果要在应用中产生特定的效果,还是要看代码中如何处理这些标签。显然FrameLayout并没有处理android:fitsSystemWindows这个属性,所以无论是否设置它都不会改变。而CoordinatorLayout则不同,我们可以查看它的源码,如下所示:privatevoidsetupForInsets(){if(Build.VERSION.SDK_INT<21){return;}if(ViewCompat.getFitsSystemWindows(this)){if(mApplyWindowInsetsListener==null){mApplyWindowInsetsListener=newandroidx.core.view.OnApplyWindowInsetsListener(){@OverridepublicWindowInsetsCompatonApplyWindowInsets(Viewv,WindowInsetsCompatinsets){returnsetWindowInsets(insets);}};}//首先应用insets监听器ViewCompat.setOnApplyWindowInsetsListener(this,mApplyWindowInsetsListener);//现在设置sysui标志,使我们能够在窗口插图中进行布局}埃尔斯e{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中再添加一个按钮:运行程序,效果如下图所示:可以看到CoordinatorLayout虽然扩展到了状态栏区域,但是它包含的按钮并不会进入状态栏区域,这样避免交互控件被阻塞的情况出现。但是有朋友会说,如果我只是想让一些子控件延伸到状态栏区域呢?比如我在CoordinatorLayout中放了一张图片。按照这个规则,图片就不会显示在状态栏后面了,这样达不到预期的效果。让我们试试这个场景。比如在CoordinatorLayout中添加一个ImageView,代码如下:<按钮android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Button"/>现在运行程序,效果如下图所示:的确,图片不会进入状态栏区域,这和我们之前解释的理论是一致的。但是很显然,这不是我们想要的效果,那么有什么办法可以解决呢?这里我们可以使用其他布局来实现。在Google提供的众多布局中,不仅CoordinatorLayout会处理android:fitsSystemWindows属性,CollapsingToolbarLayout和DrawerLayout也会处理这个属性。现在对activity_main.xml进行如下修改:<按钮android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Button"/>可以看到这里我们在ImageView外包裹了一层CollapsingToolbarLayout,并为CollapsingToolbarLayout设置了android:fitsSystemWindows属性,这样CollapsingToolbarLayout可以扩展然后我们也给ImageView设置android:fitsSystemWindows属性,这样图片就可以显示在状态栏后面了,再次运行程序,效果如下图:应该是注意CollapsingToolbarLayout必须和CoordinatorLayout一起使用,不能单独使用。因为CollapsingToolbarLayout只会调整内部控件的偏移距离,不会像CoordinatorLayout一样调用setSystemUiVisibility()函数来启用沉浸状态Bar。看到这里,相信大家已经知道如何实现沉浸式状态栏效果了,但是可能有朋友会说,由于项目限制,做不到不能使用CoordinatorLayout或CollapsingToolbarLayout,而只能使用FrameLayout或LinearLayout这样的传统布局,遇到这种情况怎么办?其实我们知道了CoordinatorLayout实现沉浸式状态栏的原理之后,自然就知道如何手动实现了,因为本质就是调用setSystemUiVisibility()函数。现在我们更改activity_main.xml以使用传统的FrameLayout布局:为了实现沉浸式状态栏的效果,我们在MainActivity中手动调用setSystemUiVisibility()函数将FrameLayout的内容扩展到状态栏区域:classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)window.statusBarColor=Color.TRANSPARENTvalframeLayout=findViewById(R.id.root_layout)frameLayout.systemUiVisibility=(SYSTEM_UI_FLAG_LAYOUT_STABLEorSYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)}}这里提醒一下,setSystemUiVisibility1函数从Android1开始就被丢弃了,Google提供了一个新的APIWindowInsetsController来实现同样的功能,但是本文不会在这方面展开。现在重新运行程序,效果如下图所示:可以看到,我们还是实现了沉浸式状态栏的效果,但是问题是FrameLayout中的按钮也延伸到了状态栏区域,这就是我们之前所说的。交互控件被状态栏挡住的问题。这个问题的原因也很好理解,因为我们之前使用了CoordinatorLayout,它已经帮我们考虑了这些东西,并且自动偏移了内部控件。而现在FrameLayout显然不会帮我们做这些事情,所以我们得自己想办法解决。其实我们可以使用setOnApplyWindowInsetsListener()函数来监听WindowInsets变化的事件。当检测到变化时,我们可以读取顶部Insets的大小,然后将控件偏移相应的距离。修改MainActivity中的代码如下:(R.id.root_layout)frameLayout.systemUiVisibility=(SYSTEM_UI_FLAG_LAYOUT_STABLEorSYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)valbutton=findViewById