這是一個很普通的表單畫面,左邊是 Android 右邊是 iOS。
當我們點擊輸入框時 Android 會自動在展開鍵盤的同時擠壓畫面以避免輸入框被鍵盤遮住,而 iOS 並不會做任何事情,因此我們需要做額外的設置。
KeyboardAvoidingView
RN 內建提供了避免鍵盤遮擋的組件 <KeyboardAvoidingView />
,使用方式是將 KeyboardAvoidingView 包在表單外層:
import { KeyboardAvoidingView } from 'react-native'
return (
<KeyboardAvoidingView style={{ flexGrow: 1 }} behavior="padding">
<View style={{ flex: 1 }}>
// form
</View>
</KeyboardAvoidingView>
)
但悲劇了…在 iOS 上一切正常,反而變成 Android 一團糟,這是因為我把 behavior 屬性設為了 padding
iOS | Android |
---|---|
behavior 屬性
behavior 屬性是用於設置當鍵盤彈出時視圖該如何調整才能不遮擋輸入框。有 height
, position
, padding
三個模式可選:
- height:鍵盤彈出時視圖的高度會縮小,通常會導致視圖的底部部分被剪裁或隱藏。
- padding:鍵盤彈出時在視圖的底部添加內邊距,所以可能會造成 UI 重疊的情況。
- position:鍵盤彈出時視圖的位置會相對於鍵盤的位置進行調整,視圖將被推動或拉伸,以避免與鍵盤重疊。
- 要注意的是設為
position
還會生成一個 View,所以需要再用contentContainerStyle
去設置這個 View 的樣式
- 要注意的是設為
對 Android 和 iOS 來說,不同的 behavior 可能會有不同的顯示結果,所以最好是雙系統都要個別設置。
以我目前的 UI 為例,height
對 iOS 無效,Android 正常
iOS | Android |
---|---|
position
直接雙系統 UI 都跑版了
測試後發現我的 UI 在 Android 適合使用 height
,而 iOS 適合使用 padding
所以可以改成:
import { Platform, KeyboardAvoidingView } from 'react-native'
return (
<KeyboardAvoidingView
style={{ flexGrow: 1 }}
behavior={Platform.select({ ios: 'padding', android: 'height' })}
>
// ...
</KeyboardAvoidingView>
)
如果覺得畫面擠壓的高度不太夠,可以再去設置 keyboardVerticalOffset
注意:
- 根據不同的 UI 結構需要自行嘗試最適合各系統的 behavior。
- 如果不願意折騰可以試試使用 react-native-keyboard-controller
KeyboardAvoidingView 與 ScrollView 一起使用
將 KeyboardAvoidingView 包在 ScrollView 外面:
import { KeyboardAvoidingView, ScrollView } from 'react-native'
export const App = () => {
return (
<KeyboardAvoidingView
style={{ flexGrow: 1 }}
behavior={Platform.select({ ios: 'padding', android: 'height' })}
>
<ScrollView>
// ...
</ScrollView>
</KeyboardAvoidingView>
)
}
Android 的鍵盤遮擋 BUG (Expo)
我遇到 BUG 的專案使用的 expo 和 RN 版本:
- Expo 49.0.3
- React Native 0.72.3
相關 issues
https://github.com/facebook/react-native/issues/38152
https://github.com/expo/expo/issues/23564
在 Expo SDK 升級到 49 之後,如果在 KeyboardAvoidingView 中使用 ScrollView,那點擊輸入框展開鍵盤時並不會擠壓(Android),並且 Development build 測試跟本地無差,要 build 出來使用才會發生。
RN 0.72.4 修復了這個 bug:
解決方式
- 升級 Expo SDK 到最新版本
- 使用
"softwareKeyboardLayoutMode": "pan"
能工作,但效果可能並不完美
點擊背景關閉鍵盤
跟點背景關閉 Modal 的寫法一樣,都是在外層包一個 TouchableWithoutFeedback
Keyboard.dismiss()
關閉 Keyboard
import { TouchableWithoutFeedback, Keyboard, KeyboardAvoidingView, Platform } from 'react-native'
// ...
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<KeyboardAvoidingView style={{ flexGrow: 1 }} behavior={Platform.select({ ios: 'padding', android: 'height' })}>
// form
</KeyboardAvoidingView>
</TouchableWithoutFeedback>