Jetpack Compose處理“導航欄、狀態列、鍵盤” 影響內容顯示的問題集錦
theme: arknights highlight: androidstudio
- 小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。
- 本文已參與 「掘力星計劃」 ,贏取創作大禮包,挑戰創作激勵金。
1.前言
寫Compose相關例子的時候,突然不怎麼使用xml,有些東西不清楚怎麼下手,就比如Compose中狀態列,導航欄沉浸,鍵盤遮擋等問題如何處理,對這方面不清楚的同學,請往下翻看看我們如何去處理它的
前方高能預警:一定要記得收藏起來,划走了可就再也找不到了😅😅🙈🙈
2.初始態
預設建立一個工程,新增如下程式碼,頁面除了“內容區域”之外,還有“導航欄、狀態列”
kotlin
setContent {
Surface(color = Color(0xFFF74C4C),modifier = Modifier.fillMaxSize()){}
}
我們可以看到狀態列顏色是style.xml中配置的,導航欄不處理的話,預設是黑色
我們下面來看Compose中如何處理“導航欄、狀態列、鍵盤”遮擋的問題
3.思考並解決
3.1-內容延伸到狀態列
上面的紅色背景有點突兀,不方便對比,我們用圖片來代替紅色背景
首先把狀態列顏色設定透明,下面提供大家簡單的三種方式來設定狀態列顏色,原理都是一樣的
,你隨便使用哪種方式都可以
- 一:style.xml設定狀態列透明色
xml
<item name="android:statusBarColor">@android:color/transparent</item>
- 二:window設定狀態列透明色
kotlin
window.statusBarColor = ResourcesCompat.getColor(resources,android.R.color.transparent,null)
- 三:systemuicontroller設定狀態列透明色
我們需要使用這個元件庫: Jetpack Compose - Accompanist 元件庫
gradle
implementation "com.google.accompanist:accompanist-systemuicontroller:<version>"
程式碼使用如下,原理也是一樣的最終都是通過window來設定的
kotlin
val systemUiController = rememberSystemUiController()
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)
我們要把內容延伸到狀態列,可以使用如下方法:
kotlin
WindowCompat.setDecorFitsSystemWindows(window,false)
3.2-狀態列遮擋列表問題
上面我們使用圖片可以看到,效果還是不錯的,如果不是圖片,我們是列表,這個時候第一位的內容是不是跑到螢幕外面了?
如果列表足夠長,最後一個條目也無法完整顯示因為第一個條目頂出到狀態列外面了
列表“第一個”和“最後一個”都無法正常顯示
我們來一步一步的解決這個問題,我們先解決狀態列遮擋第一個條目的內容;
我們需要有一個狀態列這麼高的大小,作為內邊距
來保證安全邊距,我們在使用LazyColumn的時候,可以看到內部有個contentPadding屬性:為整個內容新增的內部填充,不是針對單獨的item進行填充的
;
那麼我們如何獲取“狀態列”的高度呢?
在Compose中有兩種方式獲取:
```kotlin //方式一:系統狀態列的高度 fun Resources.getStatusBarHeight():Int { var statusBarHeight = 0 val resourceId = getIdentifier("status_bar_height", "dimen", "android") if(resourceId >0 ){ statusBarHeight = getDimensionPixelSize(resourceId) } return statusBarHeight }
//方式二:系統狀態列的高度
//1.先依賴lib庫
implementation "com.google.accompanist:accompanist-insets:
```kotlin ProvideWindowInsets { //方式一獲取到的“狀態列高度” //注意:此種方式獲取到,設定僅PaddingValues是四個方向全部都有值 /val sbPaddingValues = PaddingValues(with(LocalDensity.current) { LocalContext.current.resources.getStatusBarHeight().toDp() })/
//方式二獲取到的“狀態列高度”
//注意:這個方式獲取到的PaddingValues只有頂部狀態列方向有值,其他方向為0.dp
val sbPaddingValues = rememberInsetsPaddingValues(LocalWindowInsets.current.statusBars)
LazyColumn(modifier = Modifier.fillMaxSize()
.background(Color(0xFFDA8E70)),
contentPadding = sbPaddingValues
) {
items(12) { index ->
Text(
text = "Item:$index", color = Color.Black, fontSize = 20.sp,
modifier = Modifier.padding(5.dp).height(60.dp)
)
Divider()
}
}
} ```
狀態列遮擋問題處理
我們可以看到狀態列不再遮擋,但是導航欄會遮擋,上面我們使用的是方式二,只有頂部才會有值,不建議設定四個方向都是狀態列高度
,設定之後就會影響介面其他元素邊距,那麼如何正確的讓底部導航欄不遮擋住列表最後一個條目呢?請往下看
3.3-導航欄遮擋列表問題
看完上面狀態列遮擋的處理方法之後,導航欄不也是同樣嗎?稍微不同的是,我們不能在通過類似resId方式獲取,現在手機都可以動態更換狀態列了,所以我們還是使用LocalWindowInsets來動態獲取
kotlin
ProvideWindowInsets {
//獲取導航欄的高度
val navPaddingValues = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars)
LazyColumn(modifier = Modifier
.fillMaxSize()
.background(Color(0xFFDA8E70)),
contentPadding = navPaddingValues
) {
......
}
}
導航欄遮擋問題處理
可能有人看到這裡突然來了句,底部怎麼這麼大的距離?正常嗎?請仔細看一下,每個item的高度都是一樣的哦,仔細看一下是正常的;
我們把導航欄背景色設定為透明色,再來看一下效果,效果更明顯
kotlin
val systemUiController = rememberSystemUiController()
//設定導航欄透明色
systemUiController.setNavigationBarColor(Color.Transparent, darkIcons = false)
導航欄遮擋問題處理
3.4-同時處理狀態列和導航欄遮擋
我們使用LocalWindowInsets.current.systemBars來給contentPadding賦值,它可以同時獲取“狀態列、導航欄”所佔的高度,防止遮擋,這個方法不含鍵盤高度
同時處理 狀態列和導航欄 遮擋問題
3.5-Modifier方式
為什麼使用這個方式呢?不是所有控制元件都有contentPadding屬性可以使用,那麼我們可以通過Modifier修飾符來處理,我們同時新增navigationBarPadding()和statusBarsPadding(),或者只新增一個systemBarsPadding()(這個方法不含鍵盤高度)
來處理遮擋問題,本質上內部是使用了Modifier.padding
kotlin
ProvideWindowInsets {
LazyColumn(modifier = Modifier
.fillMaxSize()
.background(Color(0xFFDA8E70)).systemBarsPadding()
) {
......
}
}
但是Modifier的方式無法讓內容延伸到“狀態列和導航欄”下方,檢視整體是在“狀態列和導航欄”上方
Modifier.systemBarsPadding() 效果
3.6-處理鍵盤遮擋問題
我們上面提到鍵盤高度,那麼鍵盤會有什麼問題呢?我們先看下面這樣的一個例子,以經典的文字輸入框為例
```kotlin @Composable fun LoginTextField( name : String, updateName : (String) -> Unit, pwd : String, updatePwd : (String) -> Unit ){ Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom, //先讓它在導航欄上方顯示,否則會顯示在導航欄下方 //因為我們文章上面設定了內容延伸 modifier = Modifier.fillMaxSize().navigationBarsPadding() ) { OutlinedTextField( value = name, onValueChange = updateName , label = { Text("使用者名稱") }, placeholder = { Text(text = "請輸入使用者名稱") }, ...... ) OutlinedTextField( value = pwd, onValueChange = updatePwd , label = { Text("密碼") }, placeholder = { Text(text = "請輸入密碼") }, ...... keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword), //隱藏密碼內容 visualTransformation = PasswordVisualTransformation('*') ) } }
//使用如下: ProvideWindowInsets { var name by rememberSaveable { mutableStateOf("") } val updateName = { _name : String -> name = _name } var password by rememberSaveable { mutableStateOf("") } val updatePassword = { _pwd : String -> password = _pwd } LoginTextField( name = name, updateName = updateName, pwd = password, updatePwd = updatePassword ) } ``` 看一下,上面例子出現的問題,動圖如下:
鍵盤遮擋問題
很明顯,我們需要知道鍵盤的高度,才能做到不遮擋,我們可以使用Modifier.navigationBarsWithImePadding() 來做到安全不遮擋,會自動計算鍵盤開啟和關閉以及導航欄的高度最大值;
我們看替換完之後的效果,一定要注意一個事情,不要因為使用了Compose而把AndroidManifest.xml遺忘了,一定要給你所在的Activity配置如下屬性:
xml
android:windowSoftInputMode="adjustResize"
如有其他疑惑的小夥伴,可以在評論區留言
鍵盤遮擋問題修復
4.總結
(1). 我們需要如下兩個Lib庫幫助我們
gradle
implementation "com.google.accompanist:accompanist-systemuicontroller:<version>"
implementation "com.google.accompanist:accompanist-insets:<version>"
(2). 狀態列和導航欄變色
```kotlin val systemUiController = rememberSystemUiController() //分開設定,考慮到背景顏色,我們需要動態更新圖示顏色嘛 systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true) systemUiController.setNavigationBarColor(Color.Transparent, darkIcons = false)
//或者使用,直接統一兩個欄 systemUiController.setSystemBarsColor(.....) ``` (2). 如果是列表,需要內容延伸出狀態列和導航欄,可以使用contentPadding屬性,設定內容邊距
kotlin
ProvideWindowInsets {
//做到導航欄和狀態列都可以延伸內容
val paddingValues = rememberInsetsPaddingValues(LocalWindowInsets.current.systemBars)
LazyColumn(modifier = Modifier
.fillMaxSize()
.background(Color(0xFFDA8E70)),
contentPadding = navPaddingValues
) {
......
}
}
(3). 如果使用Modifier方式處理遮擋問題,無法做到內容延伸出“狀態列和導航欄”
kotlin
Modifier.navigationBarsPadding()
Modifier.statusBarsPadding()
Modifier.systemBarsPadding()
(4). 鍵盤遮擋問題
kotlin
AndroidManifer.xml配置:
android:windowSoftInputMode="adjustResize"
//防止鍵盤遮擋,文字輸入框
Modifier.navigationBarsWithImePadding()
有些小夥伴可能看完會有個疑問:列表使用contentPadding的時候和Modifier.padding內部做什麼,導致它們效果不同的呢?
感興趣的同學看一下:LazyListMeasure裡面的calculateItemsOffsets方法
往期文章推薦:
1.Android跨程序傳大圖思考及實現——附上原理分析
2.閒聊Android懸浮的“系統文字選擇選單”和“ActionMode解析”——附上原理分析
3.Jetpack Compose實現bringToFront功能——附上原理分析
4.Jetpack Compose UI建立佈局繪製流程+原理 —— 內含概念詳解(滿滿乾貨)
5.Jetpack App Startup如何使用及原理分析
6.Jetpack Compose - Accompanist 元件庫
7.原始碼分析 | ThreadedRenderer空指標問題,順便把Choreographer認識一下
8.原始碼分析 | 事件是怎麼傳遞到Activity的?
9.聊聊CountDownLatch 原始碼
10.Android正確的保活方案,不要掉進保活需求死迴圈陷進
- 鴻蒙ArkUI如何開發跨平臺應用?
- HarmonyOS玩轉ArkUI動效 - 水母動畫
- Compose挑燈夜看 - 照亮手機螢幕裡面的書本內容
- 順手修復了Jetpack Compose官方文件中的一個多點觸控示例的Bug
- 正確實踐Jetpack SplashScreen API —— 在所有Android系統上使用總結,內含原理分析
- Jetpack Compose處理“導航欄、狀態列、鍵盤” 影響內容顯示的問題集錦
- 閒聊Android懸浮的“系統文字選擇選單”和“ActionMode解析”——附上原理分析
- Jetpack Compose實現bringToFront功能——附上原理分析
- Android跨程序傳大圖思考及實現——附上原理分析