- 原文作者:Spencer Carli
- 譯文出自:掘金翻譯計划
- 譯者:rccoder
- 校對者:atuooo、ZiXYu
在使用 React Native 應用時,一個常見的問題是當你點擊文本輸入框時,鍵盤會彈出並且遮蓋住輸入框。就像這樣:
有幾種方式可以避免這種情況發生。一些方法比較簡單,另一些稍微復雜。一些是可以自定義的,一些是不能自定義的。今天,我將向你展示 3 種不同的方式來避免 React Native 應用中的鍵盤遮擋問題。
文章中所有的代碼都托管在 GitHub 上
KeyboardAvoidingView
最簡單、最容易安裝使用的方法是 KeyboardAvoidingView。這是一個核心組件,同時也非常簡單。
你可以使用這段存在鍵盤覆蓋輸入框問題的 代碼,然后更新它,使輸入框不再被覆蓋。你要做的第一件事是用 KeyboardAvoidView
替換 View
,然后給它加一個 behavior
的 prop。查看文檔的話你會發現,他可以接收三個不同的值作為參數 —— height
, padding
, position
。我發現 padding
的表現是最在我意料之內的,所以我將使用它。
import React from 'react'; import { View, TextInput, Image, KeyboardAvoidingView } from 'react-native'; import styles from './styles'; import logo from './logo.png'; const Demo = () => { return ( <KeyboardAvoidingView style={styles.container} behavior="padding" > <Image source={logo} style={styles.logo} /> <TextInput placeholder="Email" style={styles.input} /> <TextInput placeholder="Username" style={styles.input} /> <TextInput placeholder="Password" style={styles.input} /> <TextInput placeholder="Confirm Password" style={styles.input} /> <View style={{ height: 60 }} /> </KeyboardAvoidingView> ); }; export default Demo;
它的表現如下,雖然不是非常完美,但幾乎不需要任何工作量。這在我看來是相當好的。
需要注意的事,在上個實例代碼中的第 30 行,設置了一個高度為 60 的 View
。我發現keyboardAvoidingView
對最后一個元素不適用,即使是添加了 padding
/margin
屬性也不奏效。所以我添加了一個新的元素去 “撐開” 一些像素。
使用這個方法時,頂部的圖片會被推出到視圖之外。在后面我會告訴你如何解決這個問題。
針對 Android 開發者:我發現這種方法是處理這個問題最好,也是唯一的辦法。在 AndroidManifest.xml 中添加 android:windowSoftInputMode="adjustResize"。操作系統將為你解決大部分的問題,KeyboardAvoidingView 會為你解決剩下的問題。參見 這個。接下的部分可能不適用於你。
Keyboard Aware ScrollView
下一種解決辦法是使用 react-native-keyboard-aware-scroll-view,他會給你很大的沖擊。實際上它使用了 ScrollView
和 ListView
處理所有的事情(取決於你選擇的組件),讓滑動交互變得更加自然。它另外一個優點是它會自動將屏幕滾動到獲得焦點的輸入框處,這會帶來非常流暢的用戶體驗。
它的使用方法同樣非常簡單 —— 只需要替換 基礎代碼 的 View
。下面是具體代碼,我會做一些相關的說明:
import React from 'react'; import { View, TextInput, Image } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' import styles from './styles'; import logo from './logo.png'; const Demo = () => { return ( <KeyboardAwareScrollView style={{ backgroundColor: '#4c69a5' }} resetScrollToCoords={{ x: 0, y: 0 }} contentContainerStyle={styles.container} scrollEnabled={false} > <Image source={logo} style={styles.logo} /> <TextInput placeholder="Email" style={styles.input} /> <TextInput placeholder="Username" style={styles.input} /> <TextInput placeholder="Password" style={styles.input} /> <TextInput placeholder="Confirm Password" style={styles.input} /> </KeyboardAwareScrollView> );
首先你需要設置 ScrollView
的 backgroundColor
(如果你想使用滾動的話)。接下來你需要告訴默認組件在哪里,當你的鍵盤收起時,界面就會返回到默認的那個位置 —— 如果省略 View 的這個 prop,可能會導致鍵盤在關閉之后界面依舊停留在頂部。
在設置好 resetScrollToCoords
這個 prop 之后你需要設置 contentContainerStyle
—— 這本質上會替換掉你之前給 View
設置的樣式。最后一件事是禁止掉從用戶產生的滾動交互。這可能並不是完全適合你的 UI 交互(比如對於用戶需要編輯很多字段的界面),但是在這里,允許用戶滾動沒有任何意義,因為並沒有其它的內容需要用戶來進行滾動操作。
把這些所有的 prop 放到一起就會產生下面的效果,看起來很不錯:
Keyboard Module
這是迄今為止最為手動的方式,但也同時給開發者最大的控制權。你可以使用一些動畫庫來幫助實現之前看到的那種平滑滾動。
React Native 在官方文檔是沒有說 Keyboard Module 可以監聽從設備上產生的鍵盤事件。你使用的事件是 keyboardWillShow
和 keyboardWillHide
,來產生一個鍵盤展開的動畫(或者其他信息)。
當 keyboardWillShow
事件產生時,需要設置一個動畫變量到鍵盤的最終高度,並使其與鍵盤彈出滑動時間保持一致。然后你可以用這個動畫變量的值在容器的底部設置 padding
,將所有的內容上移。
我會在后面展示具體代碼,先展示一下上面所說的內容會產生的效果:
這次我將修復 UI 中的那個圖片。為此,需要使用動畫變量的值來管理圖片的高度,你可以在彈出鍵盤的同時調整圖片的高度。下面是具體代碼:
import React, { Component } from 'react'; import { View, TextInput, Image, Animated, Keyboard } from 'react-native'; import styles, { IMAGE_HEIGHT, IMAGE_HEIGHT_SMALL} from './styles'; import logo from './logo.png'; class Demo extends Component { constructor(props) { super(props); this.keyboardHeight = new Animated.Value(0); this.imageHeight = new Animated.Value(IMAGE_HEIGHT); } componentWillMount () { this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide); } componentWillUnmount() { this.keyboardWillShowSub.remove(); this.keyboardWillHideSub.remove(); } keyboardWillShow = (event) => { Animated.parallel([ Animated.timing(this.keyboardHeight, { duration: event.duration, toValue: event.endCoordinates.height, }), Animated.timing(this.imageHeight, { duration: event.duration, toValue: IMAGE_HEIGHT_SMALL, }), ]).start(); }; keyboardWillHide = (event) => { Animated.parallel([ Animated.timing(this.keyboardHeight, { duration: event.duration, toValue: 0, }), Animated.timing(this.imageHeight, { duration: event.duration, toValue: IMAGE_HEIGHT, }), ]).start(); }; render() { return ( <Animated.View style={[styles.container, { paddingBottom: this.keyboardHeight }]}> <Animated.Image source={logo} style={[styles.logo, { height: this.imageHeight }]} /> <TextInput placeholder="Email" style={styles.input} /> <TextInput placeholder="Username" style={styles.input} /> <TextInput placeholder="Password" style={styles.input} /> <TextInput placeholder="Confirm Password" style={styles.input} /> </Animated.View> ); } }; export default Demo;
它確實是一個和其他解決方案不一樣的方案。使用 Animated.View
和 Animated.Image
而非 View
和 Image
,以便可以使用動畫變量的值。有趣的部分是 keyboardWillShow
和 keyboardWillHide
,它們會改變動畫變量的參數。
這里用兩個動畫同時並行驅動 UI 的改變。會給你留下下面的印象:
雖然寫了非常多的代碼,但好歹讓整個操作看上去非常流暢。你有很大的余地去選擇你要做什么,真正的自定義與你所關心內容的互動。
Combining Options
如果想提煉一些代碼,我傾向於結合幾種情況在一起。例如: 通選方案 1 和方案 3,你就只需要關心和圖像高度相關的動畫。
隨着 UI 復雜性的增加,使用下面代碼會比方案 3 精簡很多:
import React, { Component } from 'react'; import { View, TextInput, Image, Animated, Keyboard, KeyboardAvoidingView } from 'react-native'; import styles, { IMAGE_HEIGHT, IMAGE_HEIGHT_SMALL } from './styles'; import logo from './logo.png'; class Demo extends Component { constructor(props) { super(props); this.imageHeight = new Animated.Value(IMAGE_HEIGHT); } componentWillMount () { this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide); } componentWillUnmount() { this.keyboardWillShowSub.remove(); this.keyboardWillHideSub.remove(); } keyboardWillShow = (event) => { Animated.timing(this.imageHeight, { duration: event.duration, toValue: IMAGE_HEIGHT_SMALL, }).start(); }; keyboardWillHide = (event) => { Animated.timing(this.imageHeight, { duration: event.duration, toValue: IMAGE_HEIGHT, }).start(); }; render() { return ( <KeyboardAvoidingView style={styles.container} behavior="padding" > <Animated.Image source={logo} style={[styles.logo, { height: this.imageHeight }]} /> <TextInput placeholder="Email" style={styles.input} /> <TextInput placeholder="Username" style={styles.input} /> <TextInput placeholder="Password" style={styles.input} /> <TextInput placeholder="Confirm Password" style={styles.input} /> </KeyboardAvoidingView> ); } }; export default Demo;
每種實現都有它的優點和缺點 —— 你必須選擇最適合給定用戶體驗的方案。