如何讓你的 React Native 應用在鍵盤彈出時優雅地響應


在使用 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;

每種實現都有它的優點和缺點 —— 你必須選擇最適合給定用戶體驗的方案。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM