Artigo original: How to make your React Native app respond gracefully when the keyboard pops up

por Spencer Carli

Quando você está trabalhando com aplicações do React Native, um problema comum é que o teclado aparecerá e ocultará as entradas de texto quando você focar nelas. Algo assim:

1_dcFgfha_NfuPIi4YqEnsmQ

Existem algumas maneiras de evitar isso. Algumas são simples, outras nem tanto. Algumas podem ser personalizadas. Outras, não. Hoje, vou mostrar três maneiras diferentes de evitar o teclado no React Native.

Eu coloquei todo o código-fonte para este tutorial no Github.

KeyboardAvoidingView

A solução mais simples e mais fácil de instalar é o KeyboardAvoidingView. É um componente central, mas também é bastante simples no que faz.

Você pode pegar o código de base, que tem o teclado cobrindo as entradas, e atualizá-lo para que as entradas não sejam mais cobertas. A primeira coisa que você precisa fazer é substituir o contêiner View pelo KeyboardAvoidingView e, em seguida, adicionar uma prop behavior a ele. Se você olhar a documentação, verá que ela aceita três valores diferentes – altura, preenchimento, posição. Descobri que o preenchimento funciona da maneira mais previsível. Então, é isso que eu vou usar.

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;
KeyboardAvoidingView.js

Isso nos dá o resultado que se vê abaixo. Não é perfeito, mas pelo pouquíssimo trabalho que dá, é muito bom.

1_YrvCTP6RN8zn7r7W1lJtuQ

Uma coisa a ser notada é que, na linha 30, você verá uma View que tem uma altura definida como 60px. Descobri que a Key Avoiding View não funciona com o último elemento e a configuração de preenchimento e margem (padding e margin, em inglês) não funcionou. Então, eu adicionei um novo elemento para "aumentar" tudo em alguns pixels.

A imagem na parte superior é empurrada para fora da visualização ao usar essa implementação simples. Eu vou mostrar como você pode consertar isso no final.

Usuários do Android: descobri que essa é a melhor/única opção. Ao adicionar android:windowSoftInputMode="adjustResize" ao seu AndroidManifest.xml, o sistema operacional cuidará da maior parte do trabalho para você e o KeyboardAvoidingView cuidará do resto (exemplo de AndroidManifest.xml). O restante deste artigo provavelmente não se aplicará a você.

Keyboard Aware ScrollView

A próxima opção é a react-native-keyboard-aware-scroll-view, que oferece muito retorno pelo seu investimento. Nos bastidores, você estará usando ScrollView ou ListView para lidar com tudo (dependendo do componente que você escolher), o que torna a interação de rolagem bastante perfeita. O outro grande benefício desse pacote é que ele rolará para a entrada que está em foco, o que proporciona ao usuário uma experiência agradável.

O uso também é muito fácil — você só precisa trocar o contêiner View, começando novamente com o código de base, e definir algumas opções. Aqui está o código, então eu vou descrevê-lo.

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>
  );
};

export default Demo;
KeyboardAwareScrollView.js

Primeiro, você deve definir a backgroundColor do ScrollView. Dessa maneira (como se você fosse reativar a rolagem), a backgroundColor será sempre a mesma. Em seguida, você deve dizer ao componente onde está a posição padrão para que, uma vez que o teclado seja fechado, ele volte a esse ponto - omitindo esse suporte, a visualização deve ficar presa no topo depois de fechar o teclado, assim.

1_WzOzG3P9npDpHpFj896nXA

Após a prop resetScrollToCoords, você deve definir o contentContainerStyle - que, basicamente, substitui os estilos de exibição que você tinha antes. A última coisa que estou fazendo é desabilitar a ScrollView da interação do usuário. Isso pode nem sempre fazer sentido para sua UI (interface do usuário) – como no caso de uma interface em que um usuário edita muitos campos de perfil – mas, para esta, faz muito sentido. O que não faz sentido é permitir que o usuário role manualmente, pois não há para onde rolar.

Combinando essas props, você obtém o seguinte resultado, que funciona muito bem.

1_M64W128GRs8X2IaBbSv7sA

Keyboard Module

Esta é, de longe, a opção mais manual, mas é, também, a que dá mais controle. Você usará a biblioteca Animated para ajudar a proporcionar interações suaves, como você viu antes.

O módulo Keyboard, que não está documentado no site do React Native, permite que você escute eventos de teclado emitidos pelo dispositivo. Os eventos que você usará são keyboardWillShow e keyboardWillHide, que retornam o tempo de duração da animação e a posição final do teclado (entre outras informações).

Se você estiver no Android, convém usar keyboardDidShow e o keyboardDidHide.

Quando o evento keyboardWillShow for emitido, você definirá uma variável animated para a altura final do teclado e fará com que ela seja animada pela mesma duração da animação de rolagem do teclado. Em seguida, você usa esse valor de animated para definir o preenchimento na parte inferior do contêiner para aumentar todo o conteúdo.

Mostrarei o código em um momento, mas fazer o que descrevi acima nos deixa com a seguinte experiência.

1_mOhomWU9OwZN8Kieq3Pezw

Eu quero consertar essa imagem desta vez. Para isso, você usará um valor animado para gerenciar a altura da imagem, que você ajustará quando o teclado for aberto. Aqui está o código.

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;
KeyboardModule.js

Certamente, há muito mais aqui do que nas outras soluções. Em vez de uma View ou Image, você está usando Animated.View e Animated.Image para que os valores de animated possam ser aproveitados. A parte divertida está realmente nas funções keyboardWillShow e keyboardWillHide, onde os valores de animated estão mudando.

O que está acontecendo é que os dois valores de animated estão mudando em paralelo, os quais estão sendo usados para orientar a interface do usuário. Isso deixa você com o resultado abaixo.

1_Fj87SXCLXlkKsG7aAi_5mg

É uma quantidade razoável de código, mas fica muito bom. Você tem muitas opções para o que pode fazer e pode realmente personalizar a interação para o conteúdo do seu coração.

Combinando opções

Se você quiser diminuir um pouco o código, pode combinar algumas opções, que é o que costumo fazer. Por exemplo, combinando a primeira e a terceira opções, você precisa apenas se preocupar em gerenciar e animar a altura da imagem.

O código não é muito menor do que a fonte da terceira opção, mas à medida que uma interface do usuário cresce em complexidade, ele pode ajudá-lo um pouco mais.

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;
Combo.js
1_g3clh5FFPJzBWt9egIY2cA

Cada uma dessas implementações tem seus prós e contras – você terá que escolher a mais apropriada, de acordo com a experiência do usuário que deseja

Quer aprender mais sobre como usar o React Native para criar aplicações para dispositivos móveis de alta qualidade? Inscreva-se no curso de React Native gratuito do autor!