Android 超詳細(xì)講解fitsSystemWindows屬性的使用
對(duì)于android:fitsSystemWindows這個(gè)屬性你是否感覺(jué)又熟悉又陌生呢?
熟悉是因?yàn)榇蟾胖浪梢杂脕?lái)實(shí)現(xiàn)沉浸式狀態(tài)欄的效果,陌生是因?yàn)閷?duì)它好像又不夠了解,這個(gè)屬性經(jīng)常時(shí)靈時(shí)不靈的。
其實(shí)對(duì)于android:fitsSystemWindows屬性我也是一知半解,包括我在寫(xiě)《第一行代碼》的時(shí)候?qū)@部分知識(shí)的講解也算不上精準(zhǔn)。但是由于當(dāng)時(shí)的理解對(duì)于我來(lái)說(shuō)已經(jīng)夠用了,所以也就沒(méi)再花時(shí)間繼續(xù)深入研究。
而最近因?yàn)楣ぷ鞯脑颍矣峙錾狭薬ndroid:fitsSystemWindows這個(gè)屬性,并且我之前的那些知識(shí)儲(chǔ)備已經(jīng)不夠用了。所以這次趁著這個(gè)機(jī)會(huì),我把這部分知識(shí)又重新學(xué)習(xí)了一遍,并整理成一篇文章分享給大家。
我們都不會(huì)無(wú)緣無(wú)故去接觸一個(gè)屬性。我相信用到android:fitsSystemWindows的朋友基本都是為了去實(shí)現(xiàn)沉浸式狀態(tài)欄效果的。
這里我先解釋一下什么是沉浸式狀態(tài)欄效果。
Android手機(jī)頂部用于顯示各種通知和狀態(tài)信息的這個(gè)欄叫做狀態(tài)欄。

通常情況下,我們應(yīng)用程序的內(nèi)容都是顯示在狀態(tài)欄下方的。但有時(shí)為了實(shí)現(xiàn)更好的視覺(jué)效果,我們希望將應(yīng)用程序的內(nèi)容延伸到狀態(tài)欄的背后,這種就可以稱之為沉浸式狀態(tài)欄。

那么借助android:fitsSystemWindows屬性是如何實(shí)現(xiàn)沉浸式狀態(tài)欄效果的呢?這個(gè)屬性為什么又總是時(shí)靈時(shí)不靈呢?接下來(lái)我們就來(lái)一步步學(xué)習(xí)和揭秘。
我相信按照絕大多數(shù)人的美好設(shè)想,android:fitsSystemWindows屬性就應(yīng)該像是一個(gè)開(kāi)關(guān)一樣,設(shè)置成true就可以打開(kāi)沉浸式狀態(tài)欄效果,設(shè)置成false就可以關(guān)閉沉浸式狀態(tài)欄效果。但現(xiàn)實(shí)并非如此。
下面我們通過(guò)代碼示例來(lái)演示一下。首先為了驗(yàn)證沉浸式狀態(tài)欄的效果,需要將系統(tǒng)的狀態(tài)欄改成透明色,代碼如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.statusBarColor = Color.TRANSPARENT
}
}
接下來(lái),我們給activity_main.xml的根布局加上android:fitsSystemWindows屬性,并且給該布局設(shè)置了一個(gè)背景色用于觀察效果:
<?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>
運(yùn)行一下代碼,效果如下圖所示:

通過(guò)布局的背景色我們可以看出,該布局的內(nèi)容并沒(méi)有延伸到系統(tǒng)狀態(tài)欄的背后。也就是說(shuō),即使設(shè)置了android:fitsSystemWindows屬性,我們也沒(méi)有實(shí)現(xiàn)沉浸式狀態(tài)欄效果。
但是不要著急,接下我們只需要做出一點(diǎn)小修改,如下所示:
<?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,其他都沒(méi)有任何變化。然后重新運(yùn)行程序。效果如下圖所示:

這樣就可以成功實(shí)現(xiàn)沉浸式狀態(tài)欄效果了。
話說(shuō)為什么android:fitsSystemWindows屬性,設(shè)置在CoordinatorLayout布局上就能生效,設(shè)置在FrameLayout布局上就沒(méi)有效果呢?
這是因?yàn)?,xml中的配置畢竟只是一個(gè)標(biāo)記,如果想要在應(yīng)用程序當(dāng)中產(chǎn)生具體的效果,那還是要看代碼中是如何處理這些標(biāo)記的。
很明顯,F(xiàn)rameLayout對(duì)于android:fitsSystemWindows屬性是沒(méi)有進(jìn)行處理的,所以不管設(shè)不設(shè)置都不會(huì)產(chǎn)生什么變化。
而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);
}
}
可以看到,這里當(dāng)發(fā)現(xiàn)CoordinatorLayout設(shè)置了android:fitsSystemWindows屬性時(shí),會(huì)對(duì)當(dāng)前布局的insets做一些處理,并且調(diào)用了下面一行代碼:
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
這行代碼是一切的關(guān)鍵所在。準(zhǔn)確來(lái)講,就是因?yàn)閳?zhí)行了這行代碼,我們才能將布局的內(nèi)容延伸到系統(tǒng)狀態(tài)欄區(qū)域。
是不是感覺(jué)解密了?但事實(shí)上CoordinatorLayout所做的事情還遠(yuǎn)不止這些。
因?yàn)槌两綘顟B(tài)欄其實(shí)會(huì)帶來(lái)很多問(wèn)題。讓布局的內(nèi)容延伸到狀態(tài)欄的背后,如果一些可交互的控件被狀態(tài)欄遮擋了怎么辦?這樣這些控件可能就無(wú)法點(diǎn)擊和交互了。
CoordinatorLayout為了解決這個(gè)問(wèn)題,會(huì)對(duì)所有內(nèi)部的子View都進(jìn)行一定程度的偏移,保證它們不會(huì)被狀態(tài)欄遮擋住。
比如我們?cè)贑oordinatorLayout當(dāng)中再添加一個(gè)按鈕:
<?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>
運(yùn)行一下程序,效果如下圖所示:

可以看到,雖然CoordinatorLayout延伸到了狀態(tài)欄區(qū)域,但是它所包含的按鈕是不會(huì)進(jìn)入狀態(tài)欄區(qū)域的,這樣就避免了可交互控件被遮擋的情況出現(xiàn)。
但有的朋友會(huì)說(shuō),如果有些子控件我就是想要讓它也延伸到狀態(tài)欄區(qū)域內(nèi)呢?比如我在CoordinatorLayout內(nèi)放了一張圖片,按照這個(gè)規(guī)則,圖片也是不會(huì)顯示在狀態(tài)欄背后的,這樣就達(dá)不到想要的效果了。
我們可以來(lái)試一下這種場(chǎng)景。比如在CoordinatorLayout中再添加一個(gè)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>
現(xiàn)在運(yùn)行一下程序,效果如下圖所示:

確實(shí),圖片是不會(huì)進(jìn)入狀態(tài)欄區(qū)域的,和我們之前所解釋的理論相符合。
但是很明顯,這并不是我們想要的效果,那么有什么辦法可以解決呢?
這里我們可以借助其他布局來(lái)實(shí)現(xiàn)。在Google提供的諸多布局當(dāng)中,并不是只有CoordinatorLayout會(huì)處理android:fitsSystemWindows屬性,像CollapsingToolbarLayout、DrawerLayout也是會(huì)對(duì)這個(gè)屬性做處理的。
現(xiàn)在對(duì)activity_main.xml進(jìn)行如下修改:
<?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>
可以看到,這里我們?cè)贗mageView的外面又包裹了一層CollapsingToolbarLayout,并且給CollapsingToolbarLayout也設(shè)置了android:fitsSystemWindows屬性,這樣CollapsingToolbarLayout就可以將內(nèi)容延申到狀態(tài)欄區(qū)域了。
接著我們給ImageView同樣設(shè)置了android:fitsSystemWindows屬性,如此一來(lái),就可以讓圖片顯示在狀態(tài)欄的背后了。
重新運(yùn)行一下程序,效果如下圖所示:

需要注意的是,CollapsingToolbarLayout一定要結(jié)合著CoordinatorLayout一起使用,而不能單獨(dú)使用。因?yàn)镃ollapsingToolbarLayout只會(huì)對(duì)內(nèi)部控件的偏移距離做出調(diào)整,而不會(huì)像CoordinatorLayout那樣調(diào)用setSystemUiVisibility()函數(shù)來(lái)開(kāi)啟沉浸式狀態(tài)欄。
看到這里,相信大家都已經(jīng)知道應(yīng)該如何去實(shí)現(xiàn)沉浸式狀態(tài)欄效果了。但是可能有的朋友會(huì)說(shuō),由于項(xiàng)目限制的原因,他們無(wú)法使用CoordinatorLayout或CollapsingToolbarLayout,而是只能使用像FrameLayout或LinearLayout這樣的傳統(tǒng)布局,這種情況怎么辦呢?
其實(shí)我們知道CoordinatorLayout實(shí)現(xiàn)沉浸式狀態(tài)欄的原理之后,自然也就知道如何自己手動(dòng)實(shí)現(xiàn)了,因?yàn)楸举|(zhì)就是調(diào)用setSystemUiVisibility()函數(shù)。
現(xiàn)在我們將activity_main.xml改成用傳統(tǒng)FrameLayout布局的寫(xiě)法:
<?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>
為了實(shí)現(xiàn)沉浸式狀態(tài)欄的效果,我們手動(dòng)在MainActivity當(dāng)中調(diào)用setSystemUiVisibility()函數(shù),來(lái)將FrameLayout的內(nèi)容延伸到狀態(tài)欄區(qū)域:
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)
}
}
這里提醒一點(diǎn),setSystemUiVisibility()函數(shù)其實(shí)已經(jīng)被廢棄了。從Android 11開(kāi)始,Google提供了一個(gè)新的API WindowInsetsController來(lái)實(shí)現(xiàn)同樣的功能,不過(guò)本篇文章就不往這方面展開(kāi)了。
現(xiàn)在重新運(yùn)行一下程序,效果如下圖所示:

可以看到,現(xiàn)在我們?nèi)匀粚?shí)現(xiàn)了沉浸式狀態(tài)欄的效果,但問(wèn)題是FrameLayout中的按鈕也延伸到狀態(tài)欄區(qū)域了,這就是前面所說(shuō)的可交互控件被狀態(tài)欄遮擋的問(wèn)題。
出現(xiàn)這個(gè)問(wèn)題的原因也很好理解,因?yàn)橹拔覀兪鞘褂玫腃oordinatorLayout嘛,它已經(jīng)幫我們考慮好到這些事情,自動(dòng)會(huì)將內(nèi)部的控件進(jìn)行偏移。而現(xiàn)在FrameLayout顯然是不會(huì)幫我們做這些事情的,所以我們得想辦法自己解決。
這里其實(shí)可以借助setOnApplyWindowInsetsListener()函數(shù)去監(jiān)聽(tīng)WindowInsets發(fā)生變化的事件,當(dāng)有監(jiān)聽(tīng)到發(fā)生變化時(shí),我們可以讀取頂部Insets的大小,然后對(duì)控件進(jìn)行相應(yīng)距離的偏移。
修改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
}
}
}
可以看到,當(dāng)監(jiān)聽(tīng)到WindowInsets發(fā)生變化時(shí),我們調(diào)用systemWindowInsetTop即可獲取到狀態(tài)欄的高度,然后對(duì)不需要延伸到狀態(tài)欄區(qū)域的控件進(jìn)行相應(yīng)的偏移即可。
重新運(yùn)行程序,效果如下圖所示:

好了,到這里為止,我們就將實(shí)現(xiàn)沉浸式狀態(tài)欄背后的原理,以及具體的多種實(shí)現(xiàn)方式都介紹完了。
這次你學(xué)懂a(chǎn)ndroid:fitsSystemWindows屬性了嗎?
到此這篇關(guān)于Android 超詳細(xì)講解fitsSystemWindows屬性的使用的文章就介紹到這了,更多相關(guān)Android fitsSystemWindows屬性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android開(kāi)發(fā)手冊(cè)Button按鈕實(shí)現(xiàn)點(diǎn)擊音效
- Android開(kāi)發(fā)手冊(cè)自定義Switch開(kāi)關(guān)按鈕控件
- Android單選多選按鈕的使用方法
- 基于Android FileProvider 屬性配置詳解及FileProvider多節(jié)點(diǎn)問(wèn)題
- Android使用Intent的Action和Data屬性實(shí)現(xiàn)點(diǎn)擊按鈕跳轉(zhuǎn)到撥打電話和發(fā)送短信界面
- Android?MaterialAlertDialogBuilder修改按鈕屬性
相關(guān)文章
詳談自定義View之GridView單選 金額選擇Layout-ChooseMoneyLayout
下面小編就為大家?guī)?lái)一篇詳談自定義View之GridView單選 金額選擇Layout-ChooseMoneyLayout。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Android中使用am命令實(shí)現(xiàn)在命令行啟動(dòng)程序詳解
這篇文章主要介紹了Android中使用am命令實(shí)現(xiàn)在命令行啟動(dòng)程序詳解,本文詳細(xì)講解了am命令的語(yǔ)法,然后給出了啟動(dòng)內(nèi)置程序的操作實(shí)例,需要的朋友可以參考下2015-04-04
Android Flutter利用貝塞爾曲線畫(huà)一個(gè)小海豚
貝塞爾曲線的應(yīng)用填補(bǔ)了計(jì)算機(jī)繪制與手繪之前的差距,更能表達(dá)人想畫(huà)出的曲線。本文就將利用貝塞爾曲線繪制一個(gè)可愛(ài)的小海豚,需要的可以參考一下2022-04-04
android startActivityForResult的使用方法介紹
android startActivityForResult的使用方法介紹,需要的朋友可以參考一下2013-05-05
Flutter 使用Navigator進(jìn)行局部跳轉(zhuǎn)頁(yè)面的方法
這篇文章主要介紹了Flutter 使用Navigator進(jìn)行局部跳轉(zhuǎn)頁(yè)面的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Androidstudio調(diào)用攝像頭拍照并保存照片
這篇文章主要為大家詳細(xì)介紹了Androidstudio調(diào)用攝像頭拍照并保存照片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
利用源碼編譯Android系統(tǒng)的APK和可執(zhí)行命令的方法
這篇文章主要介紹了利用源碼編譯Android系統(tǒng)的APK和可執(zhí)行命令的方法,示例在Linux系統(tǒng)環(huán)境上進(jìn)行構(gòu)建,需要的朋友可以參考下2016-02-02
Flutter實(shí)現(xiàn)支付寶集五福手畫(huà)福字功能
支付寶一年一度的集五?;顒?dòng)又開(kāi)始了,其中包含了一個(gè)功能就是手寫(xiě)福字,還包括撤銷一筆,清除重寫(xiě),保存相冊(cè)等。本文將介紹如何使用Flutter實(shí)現(xiàn)這些功能,感興趣的可以了解一下2022-01-01
Kotlin 使用Lambda來(lái)設(shè)置回調(diào)的操作
這篇文章主要介紹了Kotlin 使用Lambda來(lái)設(shè)置回調(diào)的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03

