【React Native】FlatListのinitialScrollIndexを使いこなす!コード例で学ぶ実装方法

2025-05-31

initialScrollIndex の役割

通常、FlatListは一番最初のアイテム(インデックス0)から表示を開始します。しかし、特定のアイテム(例えば、以前ユーザーが見ていた位置や、ハイライトしたいアイテムなど)からリストを表示させたい場合に、initialScrollIndexを使用します。

このプロパティに表示したいアイテムのインデックス(数値)を設定すると、FlatListはそのインデックスのアイテムを初期表示位置としてスクロールします。

initialScrollIndexを使用する際には、いくつかの重要な注意点があります。

  1. getItemLayout の使用が必須: initialScrollIndexを正しく機能させるためには、getItemLayoutプロパティを必ず実装する必要がありますFlatListは、すべてのアイテムのサイズ(高さまたは幅)が不明な場合、どの位置にスクロールすれば良いかを正確に計算できません。getItemLayoutは、各アイテムの「長さ(length)」と「オフセット(offset)」、そして「インデックス(index)」をFlatListに教えるための関数です。これにより、FlatListは初期表示時に目的のアイテムまで直接ジャンプできるようになります。

    getItemLayoutの例

    getItemLayout={(data, index) => ({
      length: ITEM_HEIGHT, // 各アイテムの固定の高さ(または幅)
      offset: ITEM_HEIGHT * index, // そのアイテムまでのオフセット
      index,
    })}
    

    もしアイテムの高さ(または幅)が動的である場合は、getItemLayoutの実装がより複雑になりますが、それでも必要です。

  2. パフォーマンスへの影響: initialScrollIndexを使用すると、FlatListの「スクロールトップへの最適化」が無効になります。通常、FlatListは最初のinitialNumToRender個のアイテムを常にレンダリングしておき、パフォーマンスを向上させます。しかし、initialScrollIndexが設定されていると、指定されたインデックスからアイテムを即座にレンダリングするため、最初のレンダリングに少し時間がかかる可能性があります。

  3. 不具合の可能性: 過去には、initialScrollIndexが期待通りに機能しない、または一部のアイテムが表示されないといった不具合が報告されています。このような問題に遭遇した場合は、以下の点を確認してみてください。

    • getItemLayoutが正しく実装されているか。
    • FlatListに渡すdataが空でないか、または正しいデータが渡されているか。
    • keyExtractorプロパティが適切に設定されているか。
    • maxToRenderPerBatchなどのレンダリング関連のプロパティを調整してみる。
    • onContentSizeChangeコールバック内でscrollToIndexメソッドを呼び出すことで、代替策とする。

    例: onContentSizeChangeを使った代替案

    import React, { useRef, useEffect } from 'react';
    import { FlatList, View, Text } from 'react-native';
    
    const MyList = ({ initialIndex }) => {
      const flatListRef = useRef(null);
      const data = Array.from({ length: 100 }, (_, i) => ({ id: String(i), value: `Item ${i}` }));
    
      useEffect(() => {
        // initialScrollIndexの代わりに、FlatListが内容をロードした後にスクロールする
        if (flatListRef.current && initialIndex !== undefined && data.length > 0) {
          // コンテンツのサイズ変更が完了した後にスクロールを試みる
          // ただし、直接onContentSizeChangeで呼ぶのではなく、ある程度の遅延を持たせるか、
          // refが利用可能になったことを確認するロジックを慎重に実装する必要があります。
          // 簡単な例として、ここではrefがセットされたらすぐに呼ぶ形にしています。
          flatListRef.current.scrollToIndex({ index: initialIndex, animated: false });
        }
      }, [initialIndex, data.length]); // initialIndexまたはデータが変更されたときに再実行
    
      const renderItem = ({ item }) => (
        <View style={{ height: 50, justifyContent: 'center', alignItems: 'center', borderBottomWidth: 1, borderColor: '#ccc' }}>
          <Text>{item.value}</Text>
        </View>
      );
    
      const getItemLayout = (data, index) => ({
        length: 50, // 各アイテムの高さ
        offset: 50 * index,
        index,
      });
    
      return (
        <FlatList
          ref={flatListRef}
          data={data}
          renderItem={renderItem}
          keyExtractor={(item) => item.id}
          getItemLayout={getItemLayout}
          // initialScrollIndex={initialIndex} // ここに直接設定することも可能だが、問題があれば上記のuseEffectなどを検討
        />
      );
    };
    
    export default MyList;
    


getItemLayout が実装されていない、または不正確

問題
initialScrollIndexが指定されていても、リストが期待通りの位置にスクロールしない、またはまったくスクロールしない。

原因
FlatListは、initialScrollIndexで指定されたアイテムの位置を正確に計算するために、各アイテムのサイズ(高さまたは幅)を知る必要があります。この情報を提供しないと、FlatListはどのオフセットにスクロールすれば良いか判断できません。getItemLayoutは、この情報を提供する唯一の方法です。

トラブルシューティング

  • 動的なアイテムサイズの場合
    アイテムのサイズが動的な場合は、getItemLayoutの実装がより複雑になります。この場合、initialScrollIndexを確実に機能させることは困難であり、scrollToIndexメソッドをonContentSizeChangeonLayoutイベントで遅延して呼び出すなどの代替策を検討する必要があります。

  • getItemLayout の実装を確認
    各アイテムが固定の高さ(または幅)を持つ場合、以下のように実装します。

    getItemLayout={(data, index) => ({
      length: ITEM_HEIGHT, // 各アイテムの固定の高さ(垂直リストの場合)
      offset: ITEM_HEIGHT * index, // そのアイテムまでのオフセット
      index,
    })}
    
    • ITEM_HEIGHT は、実際にレンダリングされるアイテムの正確な高さ(または水平リストの場合は幅)と一致している必要があります。マージンやパディングも考慮に入れる必要があります。
    • 水平リストの場合は、lengthに幅、offsetwidth * indexを設定します。

データがロードされる前に initialScrollIndex が設定される

問題
リストのデータが非同期でロードされる場合に、initialScrollIndexが機能しない。

原因
FlatListがレンダリングされる時点でdataプロパティが空([])の場合、initialScrollIndexを設定しても、スクロールする対象のアイテムが存在しないため、効果がありません。

トラブルシューティング

FlatList の親コンポーネントの高さが不定

問題
FlatListが表示されない、またはスクロールできない。

原因
FlatList(およびScrollView)は、高さが確定している親コンポーネントの中に配置される必要があります。flex: 1や特定の高さが設定されていない場合、FlatListは自身の高さを計算できず、正しくレンダリングされません。

トラブルシューティング

  • 固定の高さを設定
    必要に応じて、FlatListまたはその親に固定の高さを設定します。

  • 親コンポーネントにflex: 1を設定
    FlatListをラップするViewflex: 1を設定して、利用可能なすべてのスペースを占めるようにします。

    <View style={{ flex: 1 }}>
      <FlatList
        // ...
      />
    </View>
    

keyExtractor が正しく設定されていない

問題
initialScrollIndexとは直接関係ありませんが、FlatListのアイテムのレンダリングやパフォーマンスに影響を与える可能性があります。アイテムの追加・削除時に意図しない挙動が見られる場合があります。

原因
keyExtractorは、リスト内の各アイテムにユニークなキーを提供するために使用されます。これにより、React Nativeはアイテムの再レンダリングを効率的に処理できます。

トラブルシューティング

  • ユニークなキーを設定
    各アイテムのIDなど、ユニークな値を使用します。

    keyExtractor={(item, index) => item.id || String(index)}
    

    可能であればitem.idのようなユニークな識別子を使用し、ない場合はindexを使うこともできますが、データが頻繁に入れ替わる場合は問題を引き起こす可能性があります。

initialNumToRender との競合、またはレンダリングバッチの問題

問題
initialScrollIndexで指定された位置より前のアイテムが表示されない、または空白の領域が表示される。

原因
FlatListはパフォーマンス最適化のために、initialNumToRenderで指定された数のアイテムを最初にレンダリングし、残りはスクロールに応じてレンダリングします。initialScrollIndexinitialNumToRenderよりも大きな値の場合、指定されたインデックスまでのアイテムがまだレンダリングされていない可能性があり、その結果、空白が表示されたり、正しくスクロールしなかったりすることがあります。

トラブルシューティング

  • windowSize を調整
    windowSizeは、FlatListがレンダリングを維持するビューポートのサイズ(アイテムの数)を決定します。この値を大きくすると、画面外に多くのアイテムがレンダリングされるため、空白の領域を見る可能性が低くなりますが、メモリ使用量が増加します。

  • maxToRenderPerBatch を調整
    FlatListは、スクロール中にバッチでアイテムをレンダリングします。maxToRenderPerBatchの値を増やすことで、一度にレンダリングするアイテムの数を増やし、空白の発生を減らすことができます。ただし、メモリ消費量が増える可能性があるため、注意が必要です。

    <FlatList
      // ...
      initialScrollIndex={someIndex}
      maxToRenderPerBatch={20} // デフォルトは10
    />
    

問題
上記の解決策を試しても問題が解決しない。

原因
initialScrollIndexは、過去にReact Nativeの特定のバージョンでバグが報告されています。

トラブルシューティング

  • 代替手段の検討
    もしinitialScrollIndexがどうしても機能しない場合、前述のscrollToIndexメソッドをuseEffectonContentSizeChangeで呼び出すなどの代替策を検討します。
  • React Nativeのバージョンを確認
    使用しているReact Nativeのバージョンが最新であるか、または既知のバグが修正されているバージョンであるかを確認します。GitHubのReact NativeリポジトリのIssuesで関連する報告がないか確認するのも良いでしょう。


基本的な使用例(固定のアイテム高さ)

この例では、すべてのリストアイテムが同じ高さを持ち、リストが特定のインデックスから開始するように設定します。

import React from 'react';
import { View, Text, FlatList, StyleSheet, SafeAreaView } from 'react-native';

// 各アイテムの固定の高さ
const ITEM_HEIGHT = 60; 

// ダミーデータ
const DATA = Array.from({ length: 100 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i + 1}`,
}));

// リストアイテムのコンポーネント
const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const App = () => {
  // 最初に表示したいアイテムのインデックス
  const initialScrollIndex = 25; // 例: 26番目のアイテムから開始 (インデックスは0から始まる)

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={({ item }) => <Item title={item.title} />}
        keyExtractor={(item) => item.id}
        initialScrollIndex={initialScrollIndex}
        // getItemLayoutは必須です!
        getItemLayout={(data, index) => (
          {
            length: ITEM_HEIGHT, // 各アイテムの高さ
            offset: ITEM_HEIGHT * index, // そのアイテムまでのオフセット
            index,
          }
        )}
        // onScrollToIndexFailed は、スクロールが失敗した場合に役立ちます
        onScrollToIndexFailed={(info) => {
          console.warn('Scroll to index failed:', info);
          // 必要に応じて、ここで代替のスクロールロジックを実装できます
          // 例: data.lengthがinitialScrollIndexより小さい場合など
        }}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    height: ITEM_HEIGHT, // getItemLayoutと一致させる
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 20,
  },
});

export default App;

解説

  • onScrollToIndexFailed: デバッグ時に役立つコールバックです。initialScrollIndexが不正な値(例: データ配列の範囲外)であった場合などに呼び出されます。
  • getItemLayout: これはinitialScrollIndexを使用する上で最も重要な部分です。FlatListはこれによって、各アイテムの正確な位置を計算し、initialScrollIndexで指定されたアイテムに直接ジャンプできます。lengthはアイテムの高さ、offsetはそのアイテムがリストの先頭からどれだけ離れているかを示します。
  • initialScrollIndex={25}: リストがレンダリングされると、インデックス25(26番目のアイテム)が画面の先頭に表示されるようにスクロールします。
  • ITEM_HEIGHT: 各アイテムの固定の高さを定義しています。これがgetItemLayoutで正確に参照される必要があります。

水平リストでの使用例

horizontalプロパティをtrueに設定し、getItemLayoutlengthoffsetを幅に基づいて計算します。

import React from 'react';
import { View, Text, FlatList, StyleSheet, SafeAreaView, Dimensions } from 'react-native';

const ITEM_WIDTH = Dimensions.get('window').width / 3; // 画面幅の1/3を各アイテムの幅とする

const DATA = Array.from({ length: 50 }, (_, i) => ({
  id: String(i),
  title: `H-アイテム ${i + 1}`,
}));

const Item = ({ title }) => (
  <View style={styles.horizontalItem}>
    <Text style={styles.horizontalTitle}>{title}</Text>
  </View>
);

const AppHorizontal = () => {
  const initialScrollIndex = 15; // 例: 16番目のアイテムから開始

  return (
    <SafeAreaView style={styles.horizontalContainer}>
      <Text style={styles.sectionTitle}>Horizontal FlatList</Text>
      <FlatList
        data={DATA}
        renderItem={({ item }) => <Item title={item.title} />}
        keyExtractor={(item) => item.id}
        horizontal // 水平スクロールを有効にする
        initialScrollIndex={initialScrollIndex}
        getItemLayout={(data, index) => (
          {
            length: ITEM_WIDTH, // 各アイテムの幅
            offset: ITEM_WIDTH * index, // そのアイテムまでのオフセット
            index,
          }
        )}
        onScrollToIndexFailed={(info) => {
          console.warn('Horizontal scroll to index failed:', info);
        }}
        showsHorizontalScrollIndicator={false} // スクロールインジケーターを非表示
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  horizontalContainer: {
    flex: 1,
    marginTop: 20,
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 22,
    fontWeight: 'bold',
    marginLeft: 16,
    marginBottom: 10,
  },
  horizontalItem: {
    backgroundColor: '#a2d9ff',
    padding: 10,
    marginHorizontal: 5,
    width: ITEM_WIDTH, // getItemLayoutと一致させる
    height: 100, // 高さは任意
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 8,
  },
  horizontalTitle: {
    fontSize: 16,
    textAlign: 'center',
  },
});

export default AppHorizontal;

解説

  • ITEM_WIDTH: アイテムの幅を定義し、getItemLayoutlengthに渡します。
  • horizontal: trueに設定することでリストが水平にスクロールします。

initialScrollIndexがうまく機能しない場合や、動的なデータロード後にスクロールしたい場合に、scrollToIndexメソッドをRefと共に使用する例です。

import React, { useRef, useEffect, useState } from 'react';
import { View, Text, FlatList, StyleSheet, SafeAreaView, Button, ActivityIndicator } from 'react-native';

const ITEM_HEIGHT = 70;

const AppWithScrollToIndex = () => {
  const flatListRef = useRef(null);
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [initialIndexToScroll, setInitialIndexToScroll] = useState(null);

  // データロードのシミュレーション
  useEffect(() => {
    setTimeout(() => {
      const newData = Array.from({ length: 150 }, (_, i) => ({
        id: String(i),
        title: `動的アイテム ${i + 1}`,
      }));
      setData(newData);
      setLoading(false);
      // データロード後にスクロールしたいインデックスを設定
      setInitialIndexToScroll(75); 
    }, 2000); // 2秒後にデータをロード
  }, []);

  // データがロードされ、FlatListのRefが利用可能になったらスクロール
  useEffect(() => {
    if (flatListRef.current && data.length > 0 && initialIndexToScroll !== null) {
      // scrollToIndexを非同期で呼び出すことで、レンダリングが完了してからスクロールを試みます
      // sometimes a slight delay helps if rendering is not fully complete
      setTimeout(() => {
        flatListRef.current.scrollToIndex({
          index: initialIndexToScroll,
          animated: true, // アニメーションを有効にする
          viewPosition: 0.5, // 画面の中央にアイテムを配置
        });
      }, 100); 
    }
  }, [data, initialIndexToScroll]); // dataまたはinitialIndexToScrollが変更されたときに実行

  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Text style={styles.title}>{item.title}</Text>
    </View>
  );

  const getItemLayout = (data, index) => (
    {
      length: ITEM_HEIGHT,
      offset: ITEM_HEIGHT * index,
      index,
    }
  );

  if (loading) {
    return (
      <SafeAreaView style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#0000ff" />
        <Text>データをロード中...</Text>
      </SafeAreaView>
    );
  }

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.sectionTitle}>`scrollToIndex` を使用した例</Text>
      <FlatList
        ref={flatListRef}
        data={data}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        getItemLayout={getItemLayout}
        // initialScrollIndex はここでは使用しない
      />
      <Button
        title="インデックス0にスクロール"
        onPress={() => flatListRef.current?.scrollToIndex({ index: 0, animated: true })}
      />
      <Button
        title="インデックス99にスクロール"
        onPress={() => flatListRef.current?.scrollToIndex({ index: 99, animated: true })}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  sectionTitle: {
    fontSize: 22,
    fontWeight: 'bold',
    marginLeft: 16,
    marginBottom: 10,
  },
  item: {
    backgroundColor: '#d1f7c4',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    height: ITEM_HEIGHT,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
  },
});

export default AppWithScrollToIndex;
  • viewPosition: scrollToIndexのオプションで、指定されたインデックスのアイテムをビューポートのどの位置に表示するかを制御します。
    • 0: トップ(または左端)
    • 0.5: 中央
    • 1: ボトム(または右端)
  • setTimeout: scrollToIndexを呼び出す前にわずかな遅延を入れることで、FlatListが完全にレンダリングされるのを待つことができます。これは、特に複雑なリストや遅いデバイスでinitialScrollIndexが期待通りに機能しない場合の一般的なトラブルシューティング手法です。
  • setInitialIndexToScroll(75): データがロードされた後、76番目のアイテムにスクロールするように設定しています。
  • useStateuseEffect: 非同期でデータをロードし、データが利用可能になったときにscrollToIndexを呼び出すロジックを実装しています。
  • useRef: FlatListのインスタンスへの参照を保持するために使用します。これにより、scrollToIndexなどのメソッドを呼び出すことができます。


主な代替手段は、FlatListメソッドを使用することです。これらのメソッドは、FlatListコンポーネントの参照(ref)を通じて呼び出されます。

scrollToIndex(params)

これがinitialScrollIndexの最も直接的な代替手段であり、最もよく使われます。initialScrollIndexと異なり、これはコンポーネントのライフサイクル中にいつでも呼び出すことができます。

主なユースケース

  • initialScrollIndexがうまく機能しない場合のフォールバック。
  • ユーザーのアクション(ボタンクリックなど)に応じて特定のアイテムにスクロールしたい場合。
  • データが非同期でロードされた後、特定のアイテムにスクロールしたい場合。

必要なプロパティ

  • viewPosition (オプション): スクロール先のアイテムをビューポートのどこに配置するかを0から1の間の数値で指定します。
    • 0: ビューポートの先頭に配置(垂直リストなら上端、水平リストなら左端)。
    • 0.5: ビューポートの中央に配置。
    • 1: ビューポートの末尾に配置(垂直リストなら下端、水平リストなら右端)。
  • viewOffset (オプション): スクロール先のアイテムの表示位置に、追加でオフセット(ピクセル単位)を適用します。
  • animated (オプション, デフォルト: true): スクロールをアニメーションするかどうか。
  • index (必須): スクロールしたいアイテムのインデックス。

注意点

  • データがまだロードされていない、またはFlatListがまだレンダリングされていない状態で呼び出すと失敗します。そのため、通常はuseEffectフックやonContentSizeChangeコールバック内で呼び出すことが推奨されます。
  • initialScrollIndexと同様に、getItemLayoutが実装されていることが強く推奨されます。 これがないと、FlatListはオフスクリーンにあるアイテムの正確な位置を計算できず、scrollToIndexが期待通りに動作しない可能性があります。

コード例

import React, { useRef, useEffect, useState } from 'react';
import { View, Text, FlatList, Button, StyleSheet, SafeAreaView } from 'react-native';

const ITEM_HEIGHT = 50; // 固定のアイテム高さ

const DATA = Array.from({ length: 100 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i + 1}`,
}));

const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const ScrollToIndexExample = () => {
  const flatListRef = useRef(null);
  const [targetIndex, setTargetIndex] = useState(null); // スクロールしたいインデックス

  useEffect(() => {
    // データがロードされた後、または何らかの条件が満たされた後にスクロールしたい場合
    // ここでは、コンポーネントがマウントされてから2秒後にインデックス50にスクロールする例
    const timer = setTimeout(() => {
      setTargetIndex(50); // 例: 51番目のアイテムにスクロール
    }, 2000);
    return () => clearTimeout(timer);
  }, []);

  useEffect(() => {
    // targetIndexが設定され、FlatListの参照が利用可能になったらスクロールを実行
    if (flatListRef.current && targetIndex !== null) {
      // わずかな遅延を入れることで、FlatListのレンダリングが完了するのを待つ
      setTimeout(() => {
        flatListRef.current.scrollToIndex({
          index: targetIndex,
          animated: true,
          viewPosition: 0.5, // 画面中央に配置
        });
        setTargetIndex(null); // 一度スクロールしたらリセット
      }, 100); 
    }
  }, [data, targetIndex]); // データとターゲットインデックスの変更を監視

  const handleScrollToBeginning = () => {
    flatListRef.current?.scrollToIndex({ index: 0, animated: true });
  };

  const handleScrollToEnd = () => {
    flatListRef.current?.scrollToIndex({ index: DATA.length - 1, animated: true, viewPosition: 1 });
  };

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        ref={flatListRef}
        data={DATA}
        renderItem={({ item }) => <Item title={item.title} />}
        keyExtractor={(item) => item.id}
        getItemLayout={(data, index) => ({
          length: ITEM_HEIGHT,
          offset: ITEM_HEIGHT * index,
          index,
        })}
        onScrollToIndexFailed={(info) => {
          console.warn("Scroll to index failed:", info);
        }}
      />
      <View style={styles.buttonContainer}>
        <Button title="先頭にスクロール" onPress={handleScrollToBeginning} />
        <Button title="末尾にスクロール" onPress={handleScrollToEnd} />
        <Button title="インデックス20にスクロール" onPress={() => setTargetIndex(20)} />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#e0f7fa',
    padding: 15,
    marginVertical: 4,
    marginHorizontal: 16,
    height: ITEM_HEIGHT,
    justifyContent: 'center',
  },
  title: {
    fontSize: 18,
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    padding: 10,
    borderTopWidth: 1,
    borderColor: '#ccc',
  },
});

export default ScrollToIndexExample;

scrollToOffset(params)

これは、指定されたピクセルオフセットにスクロールするメソッドです。

主なユースケース

  • getItemLayoutを正確に実装できないが、おおよそのスクロール位置を制御したい場合。
  • FlatList内のコンテンツの総オフセットが分かっている場合。
  • 特定のピクセル位置に正確にスクロールしたい場合。

必要なプロパティ

  • animated (オプション, デフォルト: true): アニメーションを有効にするかどうか。
  • offset (必須): スクロールしたいピクセルオフセット。垂直リストの場合はY軸、水平リストの場合はX軸。

注意点

  • initialScrollIndexの代替としてはあまり一般的ではありませんが、特定のシナリオでは役立ちます。
  • getItemLayoutを使用しない場合、このメソッドは動的なアイテムサイズを持つリストでは正確な位置を保証できません。

コード例

// FlatListのrefを使用して
flatListRef.current?.scrollToOffset({ offset: 500, animated: true }); // リストの先頭から500ピクセル下にスクロール

scrollToEnd(params)

リストの末尾にスクロールするためのショートカットメソッドです。チャットアプリケーションなどで、常に最新のメッセージを表示するために使用されることがあります。

主なユースケース

  • 初期表示でリストの最後まで見せたい場合。
  • リストの末尾に自動的にスクロールしたい場合(例: 新しいメッセージが追加されたチャットアプリ)。

必要なプロパティ

  • animated (オプション, デフォルト: true): アニメーションを有効にするかどうか。

注意点

  • データがまだロードされていない、またはFlatListがまだレンダリングされていない状態で呼び出すと失敗します。
  • initialScrollIndexと同様に、getItemLayoutがなくても動作することが多いですが、パフォーマンスと正確性のためには提供することが推奨されます。

コード例

import React, { useRef, useEffect, useState } from 'react';
import { View, Text, FlatList, Button, StyleSheet, SafeAreaView } from 'react-native';

const ITEM_HEIGHT = 50;

const AppScrollToEnd = () => {
  const flatListRef = useRef(null);
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    // 初期メッセージのロード
    setTimeout(() => {
      const initialMessages = Array.from({ length: 20 }, (_, i) => ({
        id: String(i),
        text: `初期メッセージ ${i + 1}`,
      }));
      setMessages(initialMessages);
    }, 500);
  }, []);

  // 新しいメッセージが追加されたときに末尾にスクロール
  useEffect(() => {
    if (messages.length > 0 && flatListRef.current) {
      // FlatListがレンダリングを完了するのを待つための遅延
      setTimeout(() => {
        flatListRef.current?.scrollToEnd({ animated: true });
      }, 100);
    }
  }, [messages]); // メッセージが更新されるたびに実行

  const addMessage = () => {
    const newMessage = {
      id: String(messages.length),
      text: `新しいメッセージ ${messages.length + 1}`,
    };
    setMessages((prevMessages) => [...prevMessages, newMessage]);
  };

  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Text>{item.text}</Text>
    </View>
  );

  const getItemLayout = (data, index) => (
    { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
  );

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.sectionTitle}>チャット風リスト (`scrollToEnd`)</Text>
      <FlatList
        ref={flatListRef}
        data={messages}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        getItemLayout={getItemLayout} // パフォーマンスのために推奨
      />
      <Button title="メッセージを追加" onPress={addMessage} />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  sectionTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    marginLeft: 16,
    marginBottom: 10,
  },
  item: {
    backgroundColor: '#fffbe0',
    padding: 15,
    marginVertical: 4,
    marginHorizontal: 16,
    height: ITEM_HEIGHT,
    justifyContent: 'center',
    alignItems: 'flex-start', // チャット風に左寄せ
  },
});

export default AppScrollToEnd;

チャットアプリケーションなど、新しいアイテムがリストの下に追加され、スクロール方向が逆になる(新しいメッセージが下から上へ)場合に非常に便利です。

主なユースケース

  • チャットアプリケーションのように、最新のアイテムを常に下端に表示し、スクロールによって古いアイテムが上に流れるようにしたい場合。

注意点

  • scrollToEndのようなメソッドを使用する必要がなくなるため、実装が簡素化される場合があります。
  • inverted={true}を設定すると、リストの表示順序とスクロール方向が反転します。そのため、data配列は逆順で渡す必要があります(最新のアイテムが配列の先頭に来るように)。
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, Button, StyleSheet, SafeAreaView } from 'react-native';

const ITEM_HEIGHT = 50;

const AppInvertedFlatList = () => {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    // 初期メッセージのロード(新しいものが後になるように)
    const initialMessages = Array.from({ length: 10 }, (_, i) => ({
      id: String(i),
      text: `メッセージ ${i + 1}`,
    }));
    setMessages(initialMessages);
  }, []);

  const addMessage = () => {
    const newMessage = {
      id: String(messages.length),
      text: `新しいメッセージ ${messages.length + 1}`,
    };
    // 新しいメッセージを配列の先頭に追加 (invertedがtrueなので)
    setMessages((prevMessages) => [newMessage, ...prevMessages]); 
  };

  const renderItem = ({ item }) => (
    <View style={styles.invertedItem}>
      <Text>{item.text}</Text>
    </View>
  );

  const getItemLayout = (data, index) => (
    { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
  );

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.sectionTitle}>`inverted` を使用した例</Text>
      <FlatList
        data={messages} // `inverted`を使用する場合、データは新しいものから古いものへ並べる
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        inverted // リストを反転させる
        getItemLayout={getItemLayout}
      />
      <Button title="メッセージを追加" onPress={addMessage} />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  sectionTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    marginLeft: 16,
    marginBottom: 10,
  },
  invertedItem: {
    backgroundColor: '#e6ffe6',
    padding: 15,
    marginVertical: 4,
    marginHorizontal: 16,
    height: ITEM_HEIGHT,
    justifyContent: 'center',
    alignItems: 'flex-end', // チャット風に右寄せ(自分のメッセージ)
    alignSelf: 'flex-end', // アイテム自体も右寄せ
  },
});

export default AppInvertedFlatList;