React NativeのScrollViewコンポーネントは、画面サイズを超えるコンテンツをスクロールできるコンテナです。FlatListとは異なり、ScrollViewはすべてのコンテンツを一度にレンダリングするため、少量のデータ、フォーム、既知の高さの画面に最適です。React Native Docs、2024によると、ScrollViewは垂直および水平スクロール、onScrollイベント、プログラムによる位置制御、ズームジェスチャをサポートしています。このコンポーネントはiOSではネイティブUIScrollView、AndroidではScrollViewとしてレンダリングされます。
重要ポイント
ScrollViewは、コンテンツをスクロール可能なコンテナで包むReact Nativeコンポーネントです。コンテンツが画面の境界を超える場合、ユーザーは垂直または水平にスクロールできます。ScrollViewはマウント時にすべての子要素をレンダリングします。画面に表示されるかどうかに関係ありません。
FlatListが表示されている要素のみをレンダリングする(遅延レンダリング)のとは異なり、ScrollViewは即座にすべてのネイティブビューを作成します。これにより、ScrollViewはフォーム、設定ページ、1画面の商品カードなど、子要素の数が少ない(20〜30まで)か、正確な数が事前にわからない場合に便利です。
React Native Using ScrollView、2024によると、ScrollViewはネストされたスクロール(ScrollView内のScrollViewなど)、端に達したときのバウンス効果、スクロールインジケータ、スナップ位置決め、ジェスチャ処理をサポートしています。iOSでは、ScrollViewはcontentInsetAdjustmentBehaviorを通じてSafeAreaパディングを自動的に追加します。
ScrollViewは、スクロール動作、外観、インタラクティビティを制御するための豊富なプロパティセットを提供します。主要なグループを見てみましょう:スクロール制御、視覚設定、イベント、プラットフォーム固有の機能。
horizontal — スクロール方向を垂直(デフォルト)から水平に切り替えます。scrollEnabled — 必要に応じてスクロールを無効にします。showsVerticalScrollIndicatorとshowsHorizontalScrollIndicatorはスクロールインジケータの表示を制御します。bounces(iOS)はコンテンツの端に達したときのバウンス効果を有効/無効にします。
メインイベントはonScrollで、スクロールのたびに発生し、nativeEventを含むオブジェクトを渡します。これにはcontentOffset(現在位置)、contentSize(コンテンツサイズ)、layoutMeasurement(表示領域のサイズ)が含まれます。onMomentumScrollEndは慣性スクロールの完了後に発生します。onScrollEndDrag — ユーザーが画面から指を離したときに発生します。これらのイベントは、無限スクロール、パララックス効果、遅延読み込みの実装に使用されます。
ScrollViewはrefを介したプログラム制御のためのメソッドを提供します:scrollTo({x、y、animated})は指定された座標にスクロールし、scrollToEnd({animated})はコンテンツの最後までスクロールし、flashScrollIndicators()は一時的にスクロールインジケータを表示します。
const scrollRef = useRef(null);
// 先頭にスクロール
scrollRef.current.scrollTo({ x: 0, y: 0, animated: true });
// 末尾にスクロール
scrollRef.current.scrollToEnd({ animated: true });
スクロールのプログラム制御は、チャット(最新メッセージへの自動スクロール)、フォーム(エラーフィールドへのスクロール)、カルーセル(プログラムによるスワイプ)でよく使用されます。
ScrollViewとFlatListの選択は、React Nativeにおける重要なアーキテクチャ上の決定の1つです。両方のコンポーネントがスクロールを提供しますが、根本的に異なるレンダリング戦略を使用します。ScrollViewはすべてを一度にレンダリングし、FlatListは表示されている要素のみをレンダリングします。
| 特性 | ScrollView | FlatList |
|---|---|---|
| レンダリング | すべての要素を一度に | 表示のみ + バッファ |
| パフォーマンス | 30+要素で低下 | どの数でも安定 |
| コンテンツ | 任意のコンポーネント(フォーム、カード) | 配列からの均一な要素 |
| データサイズ | 小〜中(最大30要素) | 大規模かつ無制限 |
| 水平スクロール | はい(horizontalプロップ) | はい(horizontal) |
| セクション | いいえ(そのままレンダリング) | セクションにはSectionList |
選択ルール:ScrollView — 子要素の数がわかり、20〜30を超えない画面向け(ユーザープロフィール、設定フォーム、商品ページ)。FlatList — 多数の要素が予想されるリスト向け(ニュースフィード、商品カタログ、チャットメッセージ)。コンテンツが異種混合(ヘッダー、フォーム、カード、フッター)の場合は、ScrollViewが推奨されます。FlatListは均一な要素を想定しているためです。
ScrollViewはhorizontalプロップを通じて水平スクロールをサポートしており、カルーセル、画像ギャラリー、タブ、水平リストに最適です。pagingEnabledプロップ(iOS)またはsnapToIntervalと組み合わせると、ScrollViewはページベースのナビゲーションのコンポーネントになります。
horizontalプロパティはスクロール方向を変更します。pagingEnabled(iOS)はページベースのスクロールを有効にし、スクロールは要素の境界でのみ停止します。snapToIntervalは両方のプラットフォームでスナップステップをピクセル単位で設定します。
const ImageCarousel = ({ images }) => (
<ScrollView
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
onMomentumScrollEnd={handlePageChange}
>
{images.map((img, index) => (
<Image
key={index}
source={{ uri: img }}
style={{ width: SCREEN_WIDTH, height: 250 }}
/>
))}
</ScrollView>
);
このカルーセルでは、pagingEnabledが自動的に各画像の境界にスクロールをスナップします。onMomentumScrollEndは現在のページインジケータを更新できます。このようなカルーセルはネイティブに動作し、慣性スクロールをサポートします。
標準以外のスナップステップには、snapToInterval(ピクセル単位のステップサイズ)とsnapToAlignment(start、center、end)を使用します。snapToOffsetsを使用すると、スナップ用の特定の位置の配列を指定できます。これは異なるサイズのカードがある水平リストに便利です。
2つの典型的なシナリオを見てみましょう:フォーム画面(垂直スクロール)と水平カテゴリリスト。両方とも最適な動作のために異なるプロップでScrollViewを使用しています。
プロフィール編集ページには異種混合の要素が含まれています:アバター、複数の入力フィールド、スイッチ、保存ボタン。ScrollViewは各ブロックを個別にではなく、ページ全体をスクロールできます。
const ProfileScreen = () => (
<ScrollView
contentContainerStyle={styles.container}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<AvatarPicker />
<TextInput placeholder="Name" />
<TextInput placeholder="Email" />
<TextInput placeholder="About me" multiline />
<SwitchRow label="Notifications" />
<Button title="Save" />
</ScrollView>
);
keyboardShouldPersistTaps: 'handled'プロップは、キーボードが開いているときにフォームとのスクロールと操作を可能にします。contentContainerStyleはすべてのコンテンツにパディングを適用します。このパターンはReact Nativeのすべてのフォーム画面で使用されます。
カテゴリ(チャット、フィルター、タブ)をナビゲートするために、チップ付きの水平ScrollViewが使用されます。このコンポーネントはマーケットプレイスやソーシャルメディアアプリケーションでよく見られます。
const CategoryChips = ({ categories, selected, onSelect }) => (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.chipContainer}
>
{categories.map((cat) => (
<Chip
key={cat.id}
label={cat.name}
isSelected={selected === cat.id}
onPress={() => onSelect(cat.id)}
/>
))}
</ScrollView>
);
インジケータをオフにした水平ScrollViewは、カテゴリストリップの標準です。各チップは選択状態を持つ個別のコンポーネントです。showsHorizontalScrollIndicator: falseプロップはスクロールバーを削除し、モバイルアプリユーザーの期待に合致します。
よくある質問
ScrollViewは少量のデータ(最大20〜30要素)と異種コンテンツ(フォーム、プロフィール、設定画面)に適しています。長い均一なリスト(ニュースフィード、商品カタログ、メッセージ)にはFlatListを選択してください。
onScrollイベントをonScrollEndDragまたはonMomentumScrollEndとともに使用します。現在の垂直位置はnativeEvent.contentOffset.yにあります。パフォーマンスのために、ScrollEventThrottle(iOS)を使用してonScrollの呼び出し頻度を調整します。
ScrollViewはマウント時にすべての要素をレンダリングし、それぞれにネイティブビューを作成します。50+要素があると、メモリとJSスレッドの負荷が増加し、フレームドロップが発生します。そのような場合は、遅延レンダリングのFlatListを使用してください。
bounces={false}を設定します。このプロップはiOSでのみ機能し、ScrollViewの端に達したときのスプリング効果を無効にします。Androidにはデフォルトでバウンス効果がないため、このプロップは無視されます。
ScrollViewはネイティブで無限スクロールをサポートしていません。ただし、onScrollを下端への近接チェック(contentOffset.y + 画面の高さ >= contentSize.height)と組み合わせ、要素をstateに追加できます。大量のデータには、onEndReached付きのFlatListを使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。