【ReactNative】キーボード表示時のレイアウト調整

2024/12/21
reactnative-keyboard-avoiding-view

はじめに

React Nativeでは、キーボード表示時のレイアウト調整を簡単に行うために、KeyboadAvoidingView というコンポーネントが用意されています。このコンポーネントを活用することで、入力フォームがキーボードに隠れてしまう問題を解消できます。

しかし、実際に使用してみるとその挙動がわかりづらく、思い通りに動作させるために少し工夫が必要でした。

そこで今回、KeyboardAvoidingView の使い方やポイントを整理し、備忘録として記事にまとめました。


目次

  1. 基本的な使用方法
  2. スペースを縮めて調整
  3. Viewの移動量を調整
  4. フォーカスごとの挙動変更

1. 基本的な使用方法

今回、以下のようなレイアウトを KeyboardAvoidingView を使用して調整します。


base_layout

以下のコードでは KeyboardAvoidingView を使用して、キーボード表示時のレイアウト調整を行っています。

import './global.css';
import React from 'react';
import {
  KeyboardAvoidingView,
  TextInput,
  SafeAreaView,
  Text,
  View,
  Keyboard,
  TouchableWithoutFeedback,
} from 'react-native';

function App(): React.JSX.Element {
  return (
    <SafeAreaView className="flex-1">
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView className="flex-1" behavior="padding">
          <View className="gap-y-[20px]">
            <Text>デモテキスト</Text>
            <View className="flex-row mb-60">
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
            </View>
            <View className="gap-y-[8px]">
              <Text>デモテキスト</Text>
              <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
            </View>
          </View>
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </SafeAreaView>
  );
}

export default App;

ここでは、TouchableWithoutFeedback を使用して、画面がタップ時にキーボードを閉じるように設定しています。また、KeyboardAvoidingView によってキーボード表示時のレイアウト調整を設定しています。

KeyboardAvoidingViewbehavior="padding" を設定すると、キーボードを表示時に padding-bottom が追加されるのですが、現状の実装では以下のように上手く動作しません。


don't_work

なぜ動かないかに関しては、KeyboardAvoidingView not working properly? Check your Flexbox Layout first の記事がとても分かりやすかったです。詳しい内容については、ぜひ上記の記事を確認してください。

キーボードに応じた調整を有効にするためには、justify-content: flex-end; でレイアウトを下寄せにし、その下に flex: 1 の View を配置します。 こうすることで、キーボードが表示された時に flex: 1 のスペースが縮小し、さらに View が上に移動するようになります。

// ...

function App(): React.JSX.Element {
  return (
    <SafeAreaView className="flex-1">
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView className="flex-1 justify-end" behavior="padding">
          <View className="gap-y-[20px]">
            // ...
          </View>
          <View className="flex-1" />
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </SafeAreaView>
  );
}

export default App;

work



2. スペースを縮めて調整

KeyboadAvoidingViewflex で指定されたスペースを縮小することでレイアウトを調整します。
以下のコードでは、margin(mb-60)flex(flex-[40%]) に修正して、中央部分のスペースを利用することでレイアウトを調整しています。

// ...

function App(): React.JSX.Element {
  return (
    <SafeAreaView className="flex-1">
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView className="flex-1 justify-end" behavior="padding">
          <View className="gap-y-[20px] flex-1">
            <Text>デモテキスト</Text>
            <View className="flex-row flex-[40%]">
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
            </View>
            <View className="gap-y-[8px]">
              <Text>デモテキスト</Text>
              <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
            </View>
          </View>
          <View className="flex-1" />
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </SafeAreaView>
  );
}

export default App;

reactnative_keyboard_avoiding_view

このように、flex を使用することで、縮小するスペースを調整することができます。


3. Viewの移動量を調整

KeyboadAvoidingView では、paddingmargin が縮まらないため、これらの値を調整することで View の移動量を調整できます。
以下のコードでは、margin-bottom(mb-16) を追加することで、View の移動量を調整しています。

// ...

function App(): React.JSX.Element {
  return (
    <SafeAreaView className="flex-1">
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView className="flex-1 justify-end" behavior="padding">
          <View className="gap-y-[20px] flex-1 mb-16">
            <Text>デモテキスト</Text>
            <View className="flex-row flex-[40%]">
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
            </View>
            <View className="gap-y-[8px]">
              <Text>デモテキスト</Text>
              <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
            </View>
          </View>
          <View className="flex-1" />
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </SafeAreaView>
  );
}

export default App;

distance

このように、paddingmargin を使用することで、View の移動量を調整することができます。


4. フォーカスごとの挙動変更

下のフォームにフォーカスが当たった時だけ View を移動させたいということがあったので、その際の実装方法をご紹介します。
以下のコードでは、onFocusonBlur を活用してフォームのフォーカス状態を検知し、状態に応じて margin を切り替えることで実現しています。

// ...

import React, {useState} from 'react';
import {twMerge} from 'tailwind-merge';

function App(): React.JSX.Element {
  const [isBottomFocus, setBottomFocus] = useState(false);

  return (
    <SafeAreaView className="flex-1">
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView className="flex-1 justify-end" behavior="padding">
          <View
            className={twMerge(
              'gap-y-[20px]',
              isBottomFocus ? 'mb-16' : '-mb-80',
            )}>
            <Text>デモテキスト</Text>
            <View className="flex-row mb-60">
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
              <TextInput className="h-[48px] w-1/2 border-[1px] rounded-full p-3" />
            </View>
            <View className="gap-y-[8px]">
              <Text>デモテキスト</Text>
              <TextInput
                className="h-[48px] w-full border-[1px] rounded-full p-3"
                onBlur={() => setBottomFocus(false)}
                onFocus={() => setBottomFocus(true)}
              />
              <TextInput
                className="h-[48px] w-full border-[1px] rounded-full p-3"
                onBlur={() => setBottomFocus(false)}
                onFocus={() => setBottomFocus(true)}
              />
            </View>
          </View>
          <View className="flex-1" />
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </SafeAreaView>
  );
}

export default App;

distance

このように実装することで、フォーカスごとの挙動を変更することができます。


まとめ

今回、KeyboardAvoidingView の使い方についてまとめました。

初めは KeyboardAvoidingView の挙動が掴めなかったのですが、

  • behavior="padding" を設定した時に padding-bottom が追加される
  • flex で指定されたスペースを縮小する

の2つのポイントを押さえておくことで、柔軟にレイアウトを調整することができそうです。

こちらの記事が少しでもお役に立てば幸いです。最後までご覧いただきありがとうございます。

Imakyo
imakyo

未経験からエンジニアへ転職。メインはSwiftとDartを用いたモバイルアプリ開発。個人開発や業務での経験を備忘録として発信していきます。