<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
對於android:fitsSystemWindows這個屬性你是否感覺又熟悉又陌生呢?
熟悉是因為大概知道它可以用來實現沉浸式狀態列的效果,陌生是因為對它好像又不夠了解,這個屬性經常時靈時不靈的。
其實對於android:fitsSystemWindows屬性我也是一知半解,包括我在寫《第一行程式碼》的時候對這部分知識的講解也算不上精準。但是由於當時的理解對於我來說已經夠用了,所以也就沒再花時間繼續深入研究。
而最近因為工作的原因,我又碰上了android:fitsSystemWindows這個屬性,並且我之前的那些知識儲備已經不夠用了。所以這次趁著這個機會,我把這部分知識又重新學習了一遍,並整理成一篇文章分享給大家。
我們都不會無緣無故去接觸一個屬性。我相信用到android:fitsSystemWindows的朋友基本都是為了去實現沉浸式狀態列效果的。
這裡我先解釋一下什麼是沉浸式狀態列效果。
Android手機頂部用於顯示各種通知和狀態資訊的這個欄叫做狀態列。
通常情況下,我們應用程式的內容都是顯示在狀態列下方的。但有時為了實現更好的視覺效果,我們希望將應用程式的內容延伸到狀態列的背後,這種就可以稱之為沉浸式狀態列。
那麼藉助android:fitsSystemWindows屬性是如何實現沉浸式狀態列效果的呢?這個屬性為什麼又總是時靈時不靈呢?接下來我們就來一步步學習和揭祕。
我相信按照絕大多數人的美好設想,android:fitsSystemWindows屬性就應該像是一個開關一樣,設定成true就可以開啟沉浸式狀態列效果,設定成false就可以關閉沉浸式狀態列效果。但現實並非如此。
下面我們通過程式碼範例來演示一下。首先為了驗證沉浸式狀態列的效果,需要將系統的狀態列改成透明色,程式碼如下所示:
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>
執行一下程式碼,效果如下圖所示:
通過佈局的背景色我們可以看出,該佈局的內容並沒有延伸到系統狀態列的背後。也就是說,即使設定了android:fitsSystemWindows屬性,我們也沒有實現沉浸式狀態列效果。
但是不要著急,接下我們只需要做出一點小修改,如下所示:
<?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,其他都沒有任何變化。然後重新執行程式。效果如下圖所示:
這樣就可以成功實現沉浸式狀態列效果了。
話說為什麼android:fitsSystemWindows屬性,設定在CoordinatorLayout佈局上就能生效,設定在FrameLayout佈局上就沒有效果呢?
這是因為,xml中的設定畢竟只是一個標記,如果想要在應用程式當中產生具體的效果,那還是要看程式碼中是如何處理這些標記的。
很明顯,FrameLayout對於android:fitsSystemWindows屬性是沒有進行處理的,所以不管設不設定都不會產生什麼變化。
而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>
執行一下程式,效果如下圖所示:
可以看到,雖然CoordinatorLayout延伸到了狀態列區域,但是它所包含的按鈕是不會進入狀態列區域的,這樣就避免了可互動控制元件被遮擋的情況出現。
但有的朋友會說,如果有些子控制元件我就是想要讓它也延伸到狀態列區域內呢?比如我在CoordinatorLayout內放了一張圖片,按照這個規則,圖片也是不會顯示在狀態列背後的,這樣就達不到想要的效果了。
我們可以來試一下這種場景。比如在CoordinatorLayout中再新增一個ImageView,程式碼如下所示:
<?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="@drawable/bg" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
現在執行一下程式,效果如下圖所示:
確實,圖片是不會進入狀態列區域的,和我們之前所解釋的理論相符合。
但是很明顯,這並不是我們想要的效果,那麼有什麼辦法可以解決呢?
這裡我們可以藉助其他佈局來實現。在Google提供的諸多佈局當中,並不是只有CoordinatorLayout會處理android:fitsSystemWindows屬性,像CollapsingToolbarLayout、DrawerLayout也是會對這個屬性做處理的。
現在對activity_main.xml進行如下修改:
<?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="@drawable/bg" 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屬性,如此一來,就可以讓圖片顯示在狀態列的背後了。
重新執行一下程式,效果如下圖所示:
需要注意的是,CollapsingToolbarLayout一定要結合著CoordinatorLayout一起使用,而不能單獨使用。因為CollapsingToolbarLayout只會對內部控制元件的偏移距離做出調整,而不會像CoordinatorLayout那樣呼叫setSystemUiVisibility()函數來開啟沉浸式狀態列。
看到這裡,相信大家都已經知道應該如何去實現沉浸式狀態列效果了。但是可能有的朋友會說,由於專案限制的原因,他們無法使用CoordinatorLayout或CollapsingToolbarLayout,而是隻能使用像FrameLayout或LinearLayout這樣的傳統佈局,這種情況怎麼辦呢?
其實我們知道CoordinatorLayout實現沉浸式狀態列的原理之後,自然也就知道如何自己手動實現了,因為本質就是呼叫setSystemUiVisibility()函數。
現在我們將activity_main.xml改成用傳統FrameLayout佈局的寫法:
<?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="@drawable/bg" /> <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來實現同樣的功能,不過本篇文章就不往這方面展開了。
現在重新執行一下程式,效果如下圖所示:
可以看到,現在我們仍然實現了沉浸式狀態列的效果,但問題是FrameLayout中的按鈕也延伸到狀態列區域了,這就是前面所說的可互動控制元件被狀態列遮擋的問題。
出現這個問題的原因也很好理解,因為之前我們是使用的CoordinatorLayout嘛,它已經幫我們考慮好到這些事情,自動會將內部的控制元件進行偏移。而現在FrameLayout顯然是不會幫我們做這些事情的,所以我們得想辦法自己解決。
這裡其實可以藉助setOnApplyWindowInsetsListener()函數去監聽WindowInsets發生變化的事件,當有監聽到發生變化時,我們可以讀取頂部Insets的大小,然後對控制元件進行相應距離的偏移。
修改MainActivity中的程式碼,如下所示:
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屬性內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45