PR
Expo.ioReact Native ScrollView

ScrollView のスクロールを内容に応じて ON・OFF する〜React Native〜

shopping-4974738_1920 Expo.io
スポンサーリンク

環境・条件

  • React Native の初心者向け
  • Expo 開発環境:v36.0.2
  • ScrollView関数コンポーネントで実装します。

手順

App.js を以下のように作成し、設定を追加していきます。

// App.js

import React, { memo } from "react";
import { View, Text, StyleSheet } from "react-native";

const App = () => {
  return (
    <View style={styles.container}>
      <Text>App</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  }
});
export default memo(App);

スクリーンの高さとコンテンツの高さで判断するのがポイントです。

  • Step 1
    コンテンツの最下段までスクロールできるように。

    以下は、ScrollViewSafeAreaView でラップして使う前提です。

    SafeAreaView の style に {flexGrow:1} を追加します。
    {flex:1}に設定するとコンテンツの最下段が見えなくなります。

    // App.js
    
    import React, { memo } from "react";
    import { Text, StyleSheet, SafeAreaView, ScrollView } from "react-native";
    
    const App = () => {
      return (
        <SafeAreaView style={styles.container}>
          <ScrollView contentContainerStyle={styles.scrollView}>
            <Text>App</Text>
          </ScrollView>
        </SafeAreaView>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flexGrow: 1
      },
      scrollView: {
        backgroundColor: "pink",
        alignItems: "center",
        justifyContent: "center"
      }
    });
    export default memo(App);
    
  • Step 2
    コンテンツの高さを取得します。

    onContentSizeChange props を利用します。

    ハンドラー関数 ( handleContentSizeChange ) を作成し、デフォルトでパラメーターとして渡される contentWidthcontentHeight の中で高さ ( contentHeight ) だけを利用します。

    ScrollViewonContentSizeChange={handleContentSizeChange} を追加します。

    <ScrollView
      onContentSizeChange={handleContentSizeChange}  // 追加
      ...
    >

    取得したコンテンツの高さを State ( ctxHeight ) に保存します。

    import React, { memo } from "react";
    import { SafeAreaView } from "react-native";
    ...
    
    const [ctxHeight, setCtxHeight] = React.useState(0);
    const handleContentSizeChange = (contentWidth, contentHeight) => {
      setCtxHeight(contentHeight);
    };

    ↓↓

    // App.js
    
    import React, { memo } from "react";
    import { Text, StyleSheet, SafeAreaView, ScrollView } from "react-native";
    
    const App = () => {
      const [ctxHeight, setCtxHeight] = React.useState(0);
      const handleContentSizeChange = (contentWidth, contentHeight) => {
        setCtxHeight(contentHeight);
      };
      return (
        <SafeAreaView style={styles.container}>
          <ScrollView
            contentContainerStyle={styles.scrollView}
            onContentSizeChange={handleContentSizeChange}
          >
            <Text style={styles.text}>App</Text>
          </ScrollView>
        </SafeAreaView>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flexGrow: 1
      },
      scrollView: {
        backgroundColor: "pink",
        alignItems: "center",
        justifyContent: "center"
      },
      text: {
        fontSize: 42
      }
    });
    export default memo(App);
    
  • Step 3
    スクリーンの高さを取得します。

    Dimensions か、useWindowDimensions フックを利用して取得できます。

    // Dimensions
    import { Dimensions } from "react-native";
    
    const {width, height} = Dimensions.get('window')
    // useWindowDimensions
    import { useWindowDimensions } from "react-native";
    
    const ScrollViewExampleScreen = () => {
      const window = useWindowDimensions();
    ...

    ここでは useWindowDimensionswindow.height を使用します。

  • Step 4
    Scroll 機能を ON / OFF します。

    scrollEnabled props にコンテンツの高さが大きい時だけに true になるように設定します。

    ...
    const scrollEnabled = ctxHeight > window.height;
    ...
    <ScrollView
      scrollEnabled={scrollEnabled}   // 追加
      onContentSizeChange={handleContentSizeChange}
    >
    ...
    
  • Step 5
    動作を確認します。

    <Text style={styles.text}>App</Text>に長いテキストを適宜追加してからテストを行います。

    ※ テスト方法:( StyleSheet 内 )
    1) containerflexGrowflexflexGrow に変更しながらテスト、
    2) textfontSize を 42 → 12 → 42 に変更しながらテストします。

    ↓最終版

    // App.js
    
    import React, { memo } from "react";
    import {
      StyleSheet,
      Text,
      ScrollView,
      SafeAreaView,
      useWindowDimensions
    } from "react-native";
    const App = () => {
      const window = useWindowDimensions();
      const [ctxHeight, setCtxHeight] = React.useState(0);
      const handleContentSizeChange = (contentWidth, contentHeight) => {
        setCtxHeight(contentHeight);
      };
      const scrollEnabled = ctxHeight > window.height;
      console.log("-----------------------");
      console.log("!@# window.height: ", window.height);
      console.log("!@# contentHeight: ", ctxHeight);
      console.log("!@# scrollEnabled: ", scrollEnabled);
      return (
        <SafeAreaView style={styles.container}>
          <ScrollView
            scrollEnabled={scrollEnabled}
            onContentSizeChange={handleContentSizeChange}
            style={styles.scrollView}
          >
            <Text style={styles.text}>
              Lorem Ipsum is simply dummy text of the printing and typesetting
              industry. Lorem Ipsum has been the industry's standard dummy text ever
              since the 1500s, when an unknown printer took a galley of type and
              scrambled it to make a type specimen book. It has survived not only
              five centuries, but also the leap into electronic typesetting,
              remaining essentially unchanged. It was popularised in the 1960s with
              the release of Letraset sheets containing Lorem Ipsum passages, and
              more recently with desktop publishing software like Aldus PageMaker
              including versions of Lorem Ipsum. The standard chunk of Lorem Ipsum
              used since the 1500s is reproduced below for those interested.
              Sections 1.10.32 and 1.10.33 from de Finibus Bonorum et Malorum by
              Cicero are also reproduced in their exact original form, accompanied
              by English versions from the 1914 translation by H. Rackham.
            </Text>
          </ScrollView>
        </SafeAreaView>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flexGrow: 1
      },
      scrollView: {
        flex: 1,
        backgroundColor: "pink",
        alignItems: "center",
        justifyContent: "center"
      },
      text: {
        fontSize: 42
      }
    });
    export default memo(App);
    
scrollview-01
▲ scrollEnabled = false

window.height:  896
contentHeight:  186.333328
scrollEnabled:  false

scrollview-02
▲ scrollEnabled = true

window.height: 896
contentHeight: 2155.33325
scrollEnabled: true

scrollview-03
▲ scrollEnabled = true

・window.height: 896
・contentHeight: 2155.33325
・scrollEnabled: true

判定をより正確にする(2026 年 7 月 追記)

この記事で使っている onContentSizeChangescrollEnabled は、現在も同じように使えます。API は変わっていません。

ただし、実際のアプリに組み込むと判定がずれることがあります。理由と対処を書いておきます。

画面の高さと、ScrollView の高さは別物です

本文では次のように判定しています。

const scrollEnabled = ctxHeight > window.height;

useWindowDimensions() が返すのは画面全体の高さです。一方、ScrollView が実際に表示に使える高さは、そこからいろいろ引かれた値になります。

  • ステータスバーやホームインジケータの領域
  • ナビゲーションのヘッダー
  • 下部のタブバー
  • キーボードが出ているときはその高さも

この記事のサンプルのように ScrollView が画面いっぱいに広がっている場合は、ほぼ一致するので問題になりません。しかしヘッダーやタブバーのある画面に組み込むと、実際にははみ出しているのにスクロールが効かないという状態になります。原因が分かりにくいので、覚えておくと役に立ちます。

ScrollView 自身の高さを測る

確実なのは、画面の高さではなく ScrollView そのものの高さと比べることです。onLayout で取れます。

const [ctxHeight, setCtxHeight] = useState(0);
const [viewHeight, setViewHeight] = useState(0);

const scrollEnabled = ctxHeight > viewHeight;

<ScrollView
  scrollEnabled={scrollEnabled}
  onContentSizeChange={(w, h) => setCtxHeight(h)}
  onLayout={(e) => setViewHeight(e.nativeEvent.layout.height)}
  contentContainerStyle={styles.scrollView}
>

これなら、その ScrollView がどこに置かれていても正しく判定できます。画面の高さを取得する必要もなくなるので、useWindowDimensions の import も不要になります。

SafeAreaView について

本文では react-native から SafeAreaView を import しています。これはiOS でのみ動作し、Android では何もしません。

両方のプラットフォームで余白を扱いたい場合は、react-native-safe-area-contextSafeAreaView を使います。上部だけ・下部だけといった指定もできます。Expo の公式サンプルでも、現在はこちらが使われています。

※ 本節は本文に掲載したコードを読み直して整理したものです。改めて動作検証を行ったものではありません。

まとめ

React Native で ScrollView を使うときは parent に {flexGrow:1} を設定することでiOS/Android 両方スクロールが正常に動作します。

コンテンツの高さとスクリーンの高さを比較することで、スクロール機能を必要な時だけ ON にすることができました。

コメント

タイトルとURLをコピーしました