はじめに
React Nativeでモバイルアプリを作っていると、必ずと言っていいほど登場するのが「ボトムシート」です。画面下からせり上がってくる、あの詳細情報や選択肢を表示するUIコンポーネントですね。一見シンプルに見えますが、いざ自前で実装しようとすると、ジェスチャーによるドラッグ操作、スナップポイントの計算、キーボード表示時のレイアウト調整、スクロール可能なコンテンツとの競合など、意外と多くの落とし穴があります。
React-Native-Bottom-Sheetは、こうした面倒な実装を丸ごと引き受けてくれるライブラリです。ネイティブアプリのような滑らかな操作感を、驚くほど少ないコードで実現できます。この記事では、インストールから基本的な使い方、実践的なユースケースまでを解説します。
React-Native-Bottom-Sheetとは
React-Native-Bottom-Sheetは、gorhom氏が開発するオープンソースのライブラリで、「A performant interactive bottom sheet with fully configurable options(高いパフォーマンスを持つ、柔軟に設定可能なインタラクティブなボトムシート)」というコンセプトのもとに作られています。内部ではReact Native Reanimated v3とReact Native Gesture Handler v2を利用しており、UIスレッド上でアニメーションを処理するため、JSスレッドの負荷に左右されない滑らかな動きを実現しています。
主な特徴
- 高いパフォーマンス - Reanimatedベースのアニメーションにより、ネイティブに近い滑らかなジェスチャー操作を実現
- 柔軟なスナップポイント設定 - シートが止まる高さを複数指定でき、割合(%)・固定値(px)のどちらでも指定可能
- 豊富なスクロール対応コンポーネント -
BottomSheetFlatListやBottomSheetSectionList、BottomSheetScrollViewなど、既存のリスト系コンポーネントをそのまま置き換えて使える - キーボードハンドリング - 入力フォームを含むシートでも、キーボード表示時の高さ調整を自動で行ってくれる
- モーダル対応 -
BottomSheetModalを使えば、画面上に動的にシートを出し入れするモーダル的な使い方も可能
インストール
まずは必要なパッケージをインストールします。React-Native-Bottom-SheetはReanimatedとGesture Handlerに依存しているため、両方をあわせて導入します。
# npmの場合
npm install @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler
# yarnの場合
yarn add @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler
Expoプロジェクトの場合は、依存パッケージのバージョン齟齬を避けるためにexpo installの利用がおすすめです。
npx expo install @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler
インストール後、babel.config.jsにReanimatedのプラグインを追加し、アプリのエントリーポイントをGestureHandlerRootViewで囲む必要があります。
// babel.config.js
module.exports = {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
// App.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/* アプリ本体 */}
</GestureHandlerRootView>
);
}
基本的な使い方
もっともシンプルな例として、画面下部に固定表示されるボトムシートを実装してみましょう。
import React, { useMemo } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import BottomSheet from '@gorhom/bottom-sheet';
export default function BasicExample() {
// シートが止まる高さを、画面比率で複数指定する
const snapPoints = useMemo(() => ['25%', '50%', '90%'], []);
return (
<View style={styles.container}>
<BottomSheet snapPoints={snapPoints}>
<View style={styles.content}>
<Text style={styles.title}>ボトムシートの中身</Text>
<Text>ここに任意のコンテンツを配置できます。</Text>
</View>
</BottomSheet>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f2f2f2' },
content: { flex: 1, padding: 16 },
title: { fontSize: 18, fontWeight: 'bold', marginBottom: 8 },
});
snapPointsで指定した高さの間を、ユーザーがドラッグして自由に行き来できるようになります。これだけで、ジェスチャー処理やアニメーションを一切自分で書くことなく、滑らかに動くボトムシートが完成します。
実践的なユースケース
実際のアプリでは、ボタン操作をきっかけにシートを開閉したり、リスト表示と組み合わせたりする場面が多くなります。ここではBottomSheetModalを使い、詳細情報の一覧を表示する例を紹介します。
import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import {
BottomSheetModal,
BottomSheetModalProvider,
BottomSheetFlatList,
} from '@gorhom/bottom-sheet';
const ITEMS = Array.from({ length: 20 }, (_, i) => ({
id: String(i),
label: `アイテム ${i + 1}`,
}));
function ItemListSheet() {
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const snapPoints = useMemo(() => ['40%', '80%'], []);
const handlePresentModal = useCallback(() => {
bottomSheetModalRef.current?.present();
}, []);
return (
<View style={styles.container}>
<Button title="一覧を開く" onPress={handlePresentModal} />
<BottomSheetModal ref={bottomSheetModalRef} snapPoints={snapPoints}>
<BottomSheetFlatList
data={ITEMS}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.listContent}
renderItem={({ item }) => (
<View style={styles.row}>
<Text>{item.label}</Text>
</View>
)}
/>
</BottomSheetModal>
</View>
);
}
export default function App() {
return (
<BottomSheetModalProvider>
<ItemListSheet />
</BottomSheetModalProvider>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
listContent: { paddingHorizontal: 16 },
row: { paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#eee' },
});
BottomSheetModalをBottomSheetModalProviderで囲むことで、アプリ内のどこからでもpresent()を呼ぶだけでシートを表示できます。また、通常のFlatListの代わりにBottomSheetFlatListを使うことで、シートのドラッグ操作とリストのスクロール操作が自然に共存するようになります。ECサイトの商品一覧から詳細を開く、地図アプリで検索結果を表示する、といった場面でそのまま応用できる構成です。
まとめ
React-Native-Bottom-Sheetを使うことで、これまで自前実装だと骨が折れていたジェスチャー操作・スナップポイント・キーボード対応を、宣言的でシンプルなコードに置き換えられることが分かりました。BottomSheetによるシンプルな固定表示から、BottomSheetModalを使った動的な呼び出し、BottomSheetFlatListによるリスト統合まで、用途に応じて柔軟に組み合わせられる点も大きな魅力です。
自前でボトムシートの実装に頭を悩ませているなら、ぜひ一度導入を検討してみてください。より詳細なAPIオプションについては、公式ドキュメントもあわせて参照することをおすすめします。