【React Native】FlatListのinitialScrollIndexを使いこなす!コード例で学ぶ実装方法
initialScrollIndex
の役割
通常、FlatList
は一番最初のアイテム(インデックス0)から表示を開始します。しかし、特定のアイテム(例えば、以前ユーザーが見ていた位置や、ハイライトしたいアイテムなど)からリストを表示させたい場合に、initialScrollIndex
を使用します。
このプロパティに表示したいアイテムのインデックス(数値)を設定すると、FlatList
はそのインデックスのアイテムを初期表示位置としてスクロールします。
initialScrollIndex
を使用する際には、いくつかの重要な注意点があります。
-
getItemLayout
の使用が必須:initialScrollIndex
を正しく機能させるためには、getItemLayout
プロパティを必ず実装する必要があります。FlatList
は、すべてのアイテムのサイズ(高さまたは幅)が不明な場合、どの位置にスクロールすれば良いかを正確に計算できません。getItemLayout
は、各アイテムの「長さ(length)」と「オフセット(offset)」、そして「インデックス(index)」をFlatList
に教えるための関数です。これにより、FlatList
は初期表示時に目的のアイテムまで直接ジャンプできるようになります。getItemLayoutの例
getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, // 各アイテムの固定の高さ(または幅) offset: ITEM_HEIGHT * index, // そのアイテムまでのオフセット index, })}
もしアイテムの高さ(または幅)が動的である場合は、
getItemLayout
の実装がより複雑になりますが、それでも必要です。 -
パフォーマンスへの影響:
initialScrollIndex
を使用すると、FlatList
の「スクロールトップへの最適化」が無効になります。通常、FlatList
は最初のinitialNumToRender
個のアイテムを常にレンダリングしておき、パフォーマンスを向上させます。しかし、initialScrollIndex
が設定されていると、指定されたインデックスからアイテムを即座にレンダリングするため、最初のレンダリングに少し時間がかかる可能性があります。 -
不具合の可能性: 過去には、
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
メソッドをonContentSizeChange
やonLayout
イベントで遅延して呼び出すなどの代替策を検討する必要があります。 -
getItemLayout の実装を確認
各アイテムが固定の高さ(または幅)を持つ場合、以下のように実装します。getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, // 各アイテムの固定の高さ(垂直リストの場合) offset: ITEM_HEIGHT * index, // そのアイテムまでのオフセット index, })}
ITEM_HEIGHT
は、実際にレンダリングされるアイテムの正確な高さ(または水平リストの場合は幅)と一致している必要があります。マージンやパディングも考慮に入れる必要があります。- 水平リストの場合は、
length
に幅、offset
にwidth * index
を設定します。
データがロードされる前に initialScrollIndex が設定される
問題
リストのデータが非同期でロードされる場合に、initialScrollIndex
が機能しない。
原因
FlatList
がレンダリングされる時点でdata
プロパティが空([]
)の場合、initialScrollIndex
を設定しても、スクロールする対象のアイテムが存在しないため、効果がありません。
トラブルシューティング
FlatList の親コンポーネントの高さが不定
問題
FlatList
が表示されない、またはスクロールできない。
原因
FlatList
(およびScrollView
)は、高さが確定している親コンポーネントの中に配置される必要があります。flex: 1
や特定の高さが設定されていない場合、FlatList
は自身の高さを計算できず、正しくレンダリングされません。
トラブルシューティング
-
固定の高さを設定
必要に応じて、FlatList
またはその親に固定の高さを設定します。 -
親コンポーネントにflex: 1を設定
FlatList
をラップするView
にflex: 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
で指定された数のアイテムを最初にレンダリングし、残りはスクロールに応じてレンダリングします。initialScrollIndex
がinitialNumToRender
よりも大きな値の場合、指定されたインデックスまでのアイテムがまだレンダリングされていない可能性があり、その結果、空白が表示されたり、正しくスクロールしなかったりすることがあります。
トラブルシューティング
-
windowSize を調整
windowSize
は、FlatList
がレンダリングを維持するビューポートのサイズ(アイテムの数)を決定します。この値を大きくすると、画面外に多くのアイテムがレンダリングされるため、空白の領域を見る可能性が低くなりますが、メモリ使用量が増加します。 -
maxToRenderPerBatch を調整
FlatList
は、スクロール中にバッチでアイテムをレンダリングします。maxToRenderPerBatch
の値を増やすことで、一度にレンダリングするアイテムの数を増やし、空白の発生を減らすことができます。ただし、メモリ消費量が増える可能性があるため、注意が必要です。<FlatList // ... initialScrollIndex={someIndex} maxToRenderPerBatch={20} // デフォルトは10 />
問題
上記の解決策を試しても問題が解決しない。
原因
initialScrollIndex
は、過去にReact Nativeの特定のバージョンでバグが報告されています。
トラブルシューティング
- 代替手段の検討
もしinitialScrollIndex
がどうしても機能しない場合、前述のscrollToIndex
メソッドをuseEffect
やonContentSizeChange
で呼び出すなどの代替策を検討します。 - 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
に設定し、getItemLayout
でlength
とoffset
を幅に基づいて計算します。
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
: アイテムの幅を定義し、getItemLayout
のlength
に渡します。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番目のアイテムにスクロールするように設定しています。useState
とuseEffect
: 非同期でデータをロードし、データが利用可能になったときに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;