React Native開発者必見!FlatListのrefreshing機能をマスターしてUXを向上させる方法
FlatList#refreshing
とは?
FlatList
は、大量のデータを効率的に表示するためのReact Nativeのコンポーネントです。スクロール可能なリストビューを提供し、「プル・トゥ・リフレッシュ(Pull to Refresh)」という、リストを下に引っ張ってコンテンツを更新する機能もサポートしています。
この「プル・トゥ・リフレッシュ」機能を実現するために使用されるのが、refreshing
プロパティです。
refreshing
プロパティは、**リストが現在データを更新中であるかどうかを示すブール値(真偽値)**です。
refreshing={false}
: リストが更新を完了していることを示します。ローディングインジケーターは非表示になります。refreshing={true}
: リストが現在データを取得中であることを示します。この状態のとき、リストの先頭にローディングインジケーター(スピナーなど)が表示され、ユーザーに更新処理が行われていることを視覚的に伝えます。
refreshing
プロパティは、通常、コンポーネントの**状態(state)**と連携して使用されます。
-
状態の定義: コンポーネントのstateに、
refreshing
という名前のブール値(例:isRefreshing
)を定義します。初期値はfalse
です。import React, { useState, useEffect } from 'react'; import { FlatList, Text, View, RefreshControl } from 'react-native'; function MyList() { const [data, setData] = useState([]); const [isRefreshing, setIsRefreshing] = useState(false); // ★ refreshingの状態を管理 // ... }
-
FlatList
への設定:FlatList
コンポーネントにrefreshing
プロパティとしてこのstateの値を渡し、onRefresh
プロパティには更新処理をトリガーする関数を渡します。<FlatList data={data} renderItem={({ item }) => <Text>{item.name}</Text>} keyExtractor={item => item.id.toString()} refreshing={isRefreshing} // ★ ここで状態を渡す onRefresh={handleRefresh} // ★ 更新時に呼ばれる関数 />
-
onRefresh
関数の実装:onRefresh
で指定した関数(例:handleRefresh
)では、以下の処理を行います。- まず、
isRefreshing
のstateをtrue
に設定し、ローディングインジケーターを表示させます。 - 次に、新しいデータを取得するための非同期処理(API呼び出しなど)を実行します。
- データの取得が完了したら、
isRefreshing
のstateをfalse
に戻し、ローディングインジケーターを非表示にします。
const handleRefresh = async () => { setIsRefreshing(true); // 更新開始をUIに伝える // ここで新しいデータを取得する処理を記述 // 例: await fetchNewData(); // 例: fetch('https://api.example.com/data') // .then(response => response.json()) // .then(newData => { // setData(newData); // setIsRefreshing(false); // 更新完了をUIに伝える // }) // .catch(error => { // console.error(error); // setIsRefreshing(false); // エラー時も更新完了をUIに伝える // }); // ダミーデータの例 setTimeout(() => { const newData = Array.from({ length: 5 }, (_, i) => ({ id: data.length + i, name: `新しいアイテム ${data.length + i}`, })); setData([...newData, ...data]); // 新しいデータを追加 setIsRefreshing(false); // 更新完了 }, 1500); // 1.5秒後に更新完了と仮定 };
- まず、
FlatList
のrefreshing
プロパティは、「プル・トゥ・リフレッシュ」機能において、データの更新状況をUIに反映させるために非常に重要な役割を果たします。このプロパティを適切に管理することで、ユーザーに現在のリストの状態を明確に伝え、より良いユーザーエクスペリエンスを提供することができます。
FlatList#refreshing
の一般的なエラーとトラブルシューティング
FlatList
のプル・トゥ・リフレッシュ機能は非常に便利ですが、設定や状態管理を誤ると期待通りに動作しないことがあります。ここでは、よくある問題とその解決策を説明します。
リフレッシュインジケーターがすぐに消えてしまう、または表示されない
原因
refreshing
プロパティの状態管理が正しく行われていないことが最も一般的な原因です。特に、onRefresh
が呼び出されたときにrefreshing
をtrue
に設定し、データ取得が完了した後にfalse
に戻すというサイクルが守られていないと発生します。
解決策
-
初期レンダリング時のrefreshingの値
コンポーネントの初期レンダリング時にrefreshing
がtrue
になっている場合、FlatList
が自動的にリフレッシュインジケーターを表示しようとしますが、データ取得処理がなければすぐに消えてしまいます。初期値はfalse
に設定するのが一般的です。 -
非同期処理の完了後のfalse設定
データ取得などの非同期処理が完了した後に、必ずrefreshing
をfalse
に戻しているか確認してください。これを忘れると、インジケーターが表示されっぱなしになります。 -
refreshingをtrueに設定するタイミングの確認
onRefresh
関数が呼ばれた直後に、refreshing
を管理しているstateをtrue
に設定しているか確認してください。これにより、ユーザーが引っ張った際にインジケーターが表示され始めます。const [isRefreshing, setIsRefreshing] = useState(false); const handleRefresh = async () => { setIsRefreshing(true); // ★ ここでtrueに設定する // データ取得処理 await fetchData(); setIsRefreshing(false); // データ取得完了後にfalseに戻す };
リフレッシュインジケーターが表示されっぱなしになる(消えない)
原因
これは、前述の「非同期処理の完了後にrefreshing
をfalse
に戻し忘れている」場合に発生します。何らかのエラーが発生してデータ取得が失敗した場合も、false
に戻す処理がスキップされ、インジケーターが残り続けることがあります。
解決策
-
finallyブロックでのfalse設定
データ取得処理が成功しても失敗しても、最終的にrefreshing
をfalse
にするように、try...catch...finally
構文のfinally
ブロックを使用することをお勧めします。const handleRefresh = async () => { setIsRefreshing(true); try { await fetchData(); // データ取得処理 } catch (error) { console.error("データ取得エラー:", error); // エラーハンドリング } finally { setIsRefreshing(false); // ★ 成功・失敗にかかわらずfalseに戻す } };
onRefreshが呼ばれない
原因
-
FlatListの誤ったインポート
まれに、react-native
からではなく、react-native-gesture-handler
からFlatList
をインポートしてしまっている場合があります。react-native-gesture-handler
のFlatList
は、標準のFlatList
と同じonRefresh
やrefreshing
プロパティをサポートしていない可能性があります。 -
リストの高さが足りない、またはflex: 1が設定されていない
FlatList
の親コンポーネント、またはFlatList
自体に十分な高さがない場合、プル・トゥ・リフレッシュのジェスチャーが正しく認識されないことがあります。特にFlatList
が画面全体を占めるべきなのに、高さが指定されていない場合に問題となります。 -
refreshingプロパティが渡されていない、またはboolean型でない
FlatList
はrefreshing
プロパティがboolean
型であることを期待しています。undefined
などが渡されていると、onRefresh
が呼び出されないことがあります。// エラー例: refreshing={undefined} や refreshing={null} <FlatList refreshing={myVariable} // myVariableがundefinedやnullだと問題 onRefresh={handleRefresh} />
解決策
-
正しいFlatListのインポートを確認
import { FlatList } from 'react-native';
となっていることを確認してください。 -
FlatListまたは親コンポーネントにflex: 1を設定
FlatList
が適切にスクロール可能であり、プル・トゥ・リフレッシュのジェスチャーを検出できるように、親のView
やFlatList
自体にflex: 1
を設定して、利用可能なスペースを全て占有するようにします。<View style={{ flex: 1 }}> <FlatList data={data} // ... refreshing={isRefreshing} onRefresh={handleRefresh} /> </View>
-
refreshingプロパティを必ず渡す
refreshing
プロパティには必ずtrue
またはfalse
のブール値を渡してください。<FlatList refreshing={isRefreshing} // boolean型のisRefreshingを渡す onRefresh={handleRefresh} />
iOSとAndroidで挙動が異なる(特にインジケーターの表示)
原因
プラットフォーム固有のレンダリングの違いにより、インジケーターの表示タイミングやアニメーションに若干の差が出ることがあります。特に、プログラム的にrefreshing
をtrue
にした場合に、iOSではインジケーターが表示されるがAndroidでは表示されない、といったケースが報告されています。
解決策
-
強制的なスクロール (iOSでのみ有効な場合あり)
プログラム的にリフレッシュを開始した場合(例: ボタンタップでリフレッシュ)、iOSでインジケーターが表示されないことがあります。この場合、FlatList
のscrollToOffset
メソッドを使ってわずかにスクロールさせることでインジケーターを「引き出す」ワークアラウンドが報告されています。これは、プル・トゥ・リフレッシュがユーザーのドラッグ操作によってトリガーされることを前提としているためです。// FlatListにrefを設定 const flatListRef = useRef(null); const handleProgrammaticRefresh = () => { setIsRefreshing(true); if (Platform.OS === 'ios' && flatListRef.current) { flatListRef.current.scrollToOffset({ offset: -60, animated: true }); // インジケーターを引き出すためのオフセット } fetchData().finally(() => setIsRefreshing(false)); }; <FlatList ref={flatListRef} // ... refreshing={isRefreshing} onRefresh={handleRefresh} // ユーザーがプルしたときに呼ばれる />
ただし、このワークアラウンドは特定のバージョンや状況でしか機能しない可能性があり、推奨される解決策ではありません。まずは上記の状態管理を正しく行うことが重要です。
-
RefreshControlの直接使用
FlatList
のrefreshControl
プロパティにRefreshControl
コンポーネントを直接渡すことで、より細かな制御が可能になる場合があります。import { FlatList, RefreshControl } from 'react-native'; // ... <FlatList data={data} // ... refreshControl={ <RefreshControl refreshing={isRefreshing} onRefresh={handleRefresh} // Android specific: color, progressBackgroundColor, size // iOS specific: tintColor, title, titleColor /> } />
FlatListのデータが更新されないのにインジケーターが消える
原因
これはrefreshing
のstateがfalse
に戻されているのに、FlatList
に渡しているdata
プロパティが更新されていない場合に発生します。リフレッシュインジケーターは消えますが、リストの内容は古いままで、ユーザーは混乱します。
解決策
-
データ取得後のstate更新
onRefresh
内でデータを取得した後、その新しいデータをFlatList
に渡しているdata
のstateに必ずセットしてください。const handleRefresh = async () => { setIsRefreshing(true); try { const newData = await fetchNewData(); // 新しいデータを取得 setData(newData); // ★ 取得した新しいデータをstateにセット } catch (error) { console.error("データ取得エラー:", error); } finally { setIsRefreshing(false); } };
- React DevToolsの使用
React DevToolsを使って、コンポーネントのstate(特にisRefreshing
やdata
)がどのように変化しているかをリアルタイムで確認すると、問題の特定に役立ちます。 - console.logの使用
onRefresh
が呼ばれているか、isRefreshing
のstateが期待通りに変化しているかをconsole.log
で出力して確認します。
基本的なプル・トゥ・リフレッシュの実装
最も基本的な例では、refreshing
という状態(state)を管理し、onRefresh
プロパティでその状態を切り替える関数を指定します。
import React, { useState, useEffect, useCallback } from 'react';
import {
FlatList,
Text,
View,
StyleSheet,
SafeAreaView,
ActivityIndicator, // ローディングインジケーターとして使用
} from 'react-native';
// ダミーデータ
const initialData = Array.from({ length: 10 }, (_, i) => ({
id: String(i + 1),
title: `初期アイテム ${i + 1}`,
}));
function MyRefreshingFlatList() {
const [data, setData] = useState(initialData);
const [isRefreshing, setIsRefreshing] = useState(false); // ★ refreshingの状態を管理
// データ取得のシミュレーション関数
const fetchData = async () => {
return new Promise(resolve => {
setTimeout(() => {
const newData = Array.from({ length: 5 }, (_, i) => ({
id: String(data.length + i + 1),
title: `新しいアイテム ${data.length + i + 1}`,
}));
resolve(newData);
}, 1500); // 1.5秒間の遅延をシミュレート
});
};
// プル・トゥ・リフレッシュ時に呼ばれる関数
const handleRefresh = useCallback(async () => {
setIsRefreshing(true); // リフレッシュ開始を示す(インジケーターが表示される)
const newItems = await fetchData(); // 新しいデータを取得
setData(prevData => [...newItems, ...prevData]); // 新しいデータをリストの先頭に追加
setIsRefreshing(false); // リフレッシュ完了を示す(インジケーターが非表示になる)
}, [data]); // dataが変更されたらhandleRefreshも再生成されるようにする
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
refreshing={isRefreshing} // ★ refreshingプロパティに状態を渡す
onRefresh={handleRefresh} // ★ プル・トゥ・リフレッシュ時に呼ばれる関数
ListHeaderComponent={isRefreshing ? <ActivityIndicator style={{ paddingVertical: 10 }} size="large" color="#0000ff" /> : null}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 20,
},
});
export default MyRefreshingFlatList;
解説
ListHeaderComponent
: この例では、refreshing
がtrue
の時にActivityIndicator
を表示するようにしています。FlatList
のrefreshing
とonRefresh
を使うと、デフォルトでプラットフォームごとのリフレッシュインジケーターが表示されますが、このようにカスタムのローディング表示を追加することもできます。- handleRefresh関数
- 最初に
setIsRefreshing(true)
を呼び出し、インジケーターを表示させます。 fetchData()
(ここではダミーの非同期処理)を実行し、新しいデータを取得します。setData()
で取得した新しいデータを既存のリストの先頭に追加します。- 最後に
setIsRefreshing(false)
を呼び出し、インジケーターを非表示にします。
- 最初に
onRefresh={handleRefresh}
: ユーザーがリストを下に引っ張ってリフレッシュをトリガーしたときにhandleRefresh
関数が呼び出されます。refreshing={isRefreshing}
:FlatList
にisRefreshing
の現在の値を渡します。この値がtrue
になると、プル・トゥ・リフレッシュのローディングインジケーターが表示されます。useState(false)
:isRefreshing
というstateを定義し、初期値をfalse
にしています。これは、リストが最初はリフレッシュ状態ではないことを示します。
FlatList
は内部でRefreshControl
コンポーネントを使用していますが、refreshControl
プロパティにRefreshControl
を明示的に渡すことで、より詳細なカスタマイズ(Androidでの色の変更など)が可能です。
import React, { useState, useEffect, useCallback } from 'react';
import {
FlatList,
Text,
View,
StyleSheet,
SafeAreaView,
RefreshControl, // RefreshControlをインポート
} from 'react-native';
const initialData = Array.from({ length: 10 }, (_, i) => ({
id: String(i + 1),
title: `初期アイテム ${i + 1}`,
}));
function MyCustomRefreshingFlatList() {
const [data, setData] = useState(initialData);
const [isRefreshing, setIsRefreshing] = useState(false);
const fetchData = async () => {
return new Promise(resolve => {
setTimeout(() => {
const newData = Array.from({ length: 5 }, (_, i) => ({
id: String(data.length + i + 1),
title: `追加アイテム ${data.length + i + 1}`,
}));
resolve(newData);
}, 2000); // 2秒間の遅延をシミュレート
});
};
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
try {
const newItems = await fetchData();
setData(prevData => [...newItems, ...prevData]);
} catch (error) {
console.error("データ取得エラー:", error);
// エラー時の処理
} finally {
setIsRefreshing(false); // エラーが発生しても必ずfalseに戻す
}
}, [data]);
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
refreshControl={ // ★ refreshControlプロパティを使用
<RefreshControl
refreshing={isRefreshing} // refreshingの状態を渡す
onRefresh={handleRefresh} // リフレッシュ時の処理
tintColor="#ff0000" // iOSのインジケーターの色 (赤)
title="データを更新中..." // iOSの表示テキスト
titleColor="#0000ff" // iOSのテキストの色 (青)
colors={['#9bc53d', '#f2b5d4']} // Androidのインジケーターの色 (複数の色を循環)
progressBackgroundColor="#ffffff" // Androidの背景色
/>
}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 20,
},
});
export default MyCustomRefreshingFlatList;
- RefreshControlのプロパティ
tintColor
(iOS): ローディングインジケーターの色を設定します。title
(iOS): ローディングインジケーターの下に表示されるテキストを設定します。titleColor
(iOS):title
のテキストの色を設定します。colors
(Android): Androidでインジケーターが回転する際に使用する色の配列を設定します。progressBackgroundColor
(Android): Androidでインジケーターの背景色を設定します。
refreshControl={<RefreshControl ... />}
:FlatList
のrefreshControl
プロパティにRefreshControl
コンポーネントを直接渡しています。
ScrollView と RefreshControl を組み合わせる
FlatList
は内部的にScrollView
を使用しているため、FlatList
を使わずに直接ScrollView
とRefreshControl
を組み合わせてプル・トゥ・リフレッシュを実装することも可能です。これは、リストではないがスクロール可能なコンテンツに対してプル・トゥ・リフレッシュ機能を追加したい場合に特に役立ちます。
利点
RefreshControl
のプロパティを直接設定できるため、より柔軟なカスタマイズが可能。FlatList
を使用しない場合でも、プル・トゥ・リフレッシュ機能を利用できる。
欠点
- 大量のデータを表示する場合、
FlatList
のような仮想化によるパフォーマンス最適化の恩恵を受けられない。すべてのアイテムが一度にレンダリングされるため、メモリ使用量が増え、パフォーマンスが低下する可能性がある。
コード例
import React, { useState, useCallback } from 'react';
import { ScrollView, Text, View, StyleSheet, RefreshControl, SafeAreaView } from 'react-native';
function MyRefreshingScrollView() {
const [content, setContent] = useState('初期コンテンツ');
const [isRefreshing, setIsRefreshing] = useState(false);
const fetchData = async () => {
return new Promise(resolve => {
setTimeout(() => {
const newContent = `更新されたコンテンツ: ${new Date().toLocaleTimeString()}`;
resolve(newContent);
}, 1500);
});
};
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
const updatedContent = await fetchData();
setContent(updatedContent);
setIsRefreshing(false);
}, []);
return (
<SafeAreaView style={styles.container}>
<ScrollView
contentContainerStyle={styles.scrollViewContent}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
tintColor="#007aff" // iOS
colors={['#007aff']} // Android
title="更新中..." // iOS
/>
}
>
<Text style={styles.text}>{content}</Text>
<View style={styles.spacer} /> {/* スクロール可能にするためのスペース */}
<Text style={styles.text}>下に引っ張って更新</Text>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollViewContent: {
flexGrow: 1, // コンテンツが少ない場合でもScrollViewが画面全体を占めるようにする
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 50,
},
text: {
fontSize: 24,
textAlign: 'center',
marginBottom: 20,
},
spacer: {
height: 300, // スクロール可能にするために必要な高さ
},
});
export default MyRefreshingScrollView;
カスタムのプル・トゥ・リフレッシュコンポーネントを作成する
PanResponder
やreact-native-reanimated
、react-native-gesture-handler
といった低レベルのAPIやライブラリを使用して、完全にカスタムのプル・トゥ・リフレッシュUIとロジックを実装することも可能です。これは、標準のRefreshControl
では実現できないような独自のアニメーションやインタラクションが必要な場合に検討されます。
利点
- 標準の
RefreshControl
の限界を超えることができる。 - デザインとアニメーションに関して究極の柔軟性がある。
欠点
- OSごとの挙動の違いを吸収するための考慮が必要。
- パフォーマンス最適化やジェスチャーハンドリングを自身で管理する必要がある。
- 実装が非常に複雑で、時間と労力がかかる。
実装の概念
- PanResponderまたはreact-native-gesture-handlerでジェスチャーを検出
ユーザーがリストの先頭で下に引っ張る動きを検出します。 - スクロール位置の監視
FlatList
やScrollView
のonScroll
イベントを使用して、現在のスクロール位置を監視します。リストの最上部にいるときに特定の閾値を超えて引っ張られた場合のみ、リフレッシュをトリガーします。 - アニメーションの制御
ユーザーのドラッグ量に応じてカスタムのローディングUI(Lottieアニメーションなど)を動かし、リフレッシュがトリガーされたらローディング状態のアニメーションに切り替えます。Animated
APIやreact-native-reanimated
を使用します。 - データの取得とUIの更新
refreshing
プロパティの場合と同様に、データの取得が完了したらローディングアニメーションを終了し、UIを元の状態に戻します。
(完全なコード例は非常に複雑になるため省略しますが、概念を示します)
import React, { useRef, useState } from 'react';
import { FlatList, Text, View, StyleSheet, Animated, PanResponder } from 'react-native';
const REFRESH_THRESHOLD = 100; // どれくらい引っ張るとリフレッシュをトリガーするか
const RELEASE_OFFSET = 60; // リフレッシュ中UIの表示高さ
function MyCustomPullToRefreshFlatList() {
const [data, setData] = useState(Array.from({ length: 20 }, (_, i) => ({ id: String(i), title: `アイテム ${i}` })));
const [isRefreshing, setIsRefreshing] = useState(false);
const scrollY = useRef(new Animated.Value(0)).current;
const refreshAnim = useRef(new Animated.Value(0)).current; // リフレッシュUIのアニメーション用
// スクロールイベントを監視
const onScroll = Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{ useNativeDriver: false } // useNativeDriver: true を推奨しますが、簡略化のためfalse
);
// PanResponderの設定
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => {
// 最上部にいて、下方向にドラッグしている場合にのみPanResponderを有効にする
return scrollY._value <= 0 && gestureState.dy > 0;
},
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
return scrollY._value <= 0 && gestureState.dy > 0;
},
onPanResponderMove: (evt, gestureState) => {
if (scrollY._value <= 0) {
// 下に引っ張る量に応じてリフレッシュUIを動かす
refreshAnim.setValue(Math.min(gestureState.dy, REFRESH_THRESHOLD + 20));
}
},
onPanResponderRelease: async (evt, gestureState) => {
if (gestureState.dy > REFRESH_THRESHOLD && scrollY._value <= 0) {
setIsRefreshing(true);
// リフレッシュ中のUIを表示
Animated.timing(refreshAnim, {
toValue: RELEASE_OFFSET,
duration: 200,
useNativeDriver: false,
}).start();
// データ取得処理
await new Promise(resolve => setTimeout(() => {
const newItems = Array.from({ length: 3 }, (_, i) => ({
id: `new-${Date.now()}-${i}`,
title: `新着アイテム ${data.length + i}`,
}));
setData(prevData => [...newItems, ...prevData]);
resolve();
}, 2000));
setIsRefreshing(false);
// リフレッシュUIを隠す
Animated.timing(refreshAnim, {
toValue: 0,
duration: 200,
useNativeDriver: false,
}).start();
} else {
// 閾値に達しなかった場合、UIを元に戻す
Animated.timing(refreshAnim, {
toValue: 0,
duration: 200,
useNativeDriver: false,
}).start();
}
},
})
).current;
return (
<View style={styles.container} {...panResponder.panHandlers}>
<Animated.View
style={[
styles.refreshHeader,
{
height: refreshAnim, // 引っ張る量に応じて高さを変える
opacity: refreshAnim.interpolate({
inputRange: [0, 20, REFRESH_THRESHOLD],
outputRange: [0, 0.5, 1],
extrapolate: 'clamp',
}),
},
]}
>
{isRefreshing ? (
<Text>更新中...</Text>
) : (
<Text>↓ リフレッシュ</Text>
)}
</Animated.View>
<FlatList
data={data}
renderItem={({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id}
onScroll={onScroll}
scrollEventThrottle={16} // スクロールイベントの頻度
// FlatListのデフォルトのrefreshing機能は無効にするか、併用しない
// refreshing={isRefreshing}
// onRefresh={() => {}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 50,
},
refreshHeader: {
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden', // 高さが0の時に見えないように
backgroundColor: '#e0e0e0',
position: 'absolute', // FlatListの上に重ねる
top: 0,
left: 0,
right: 0,
zIndex: 1, // FlatListより手前に表示
},
item: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
backgroundColor: '#fff',
},
});
export default MyCustomPullToRefreshFlatList;
このカスタム実装は非常に複雑であり、以下のような追加の考慮事項があります
- プラットフォームの差異
iOSとAndroidでジェスチャーの検出やアニメーションの挙動に微妙な違いがあるため、両プラットフォームでのテストと調整が必要です。 - パフォーマンス
アニメーションをスムーズにするためには、useNativeDriver
をtrue
に設定したり、react-native-reanimated
のような高性能なアニメーションライブラリを活用したりする必要があります。 - イベントの競合
FlatList
自身のスクロールジェスチャーとPanResponder
のジェスチャーが競合しないように、onMoveShouldSetPanResponder
などの条件を慎重に設定する必要があります。 - スクロールオフセットの管理
FlatList
やScrollView
の内部スクロールオフセットとジェスチャーハンドラーのオフセットを同期させる必要があります。
ほとんどのケースでは、FlatList
の標準的なrefreshing
とonRefresh
プロパティ、またはRefreshControl
を明示的に使用する方法が最も簡単で信頼性があります。
- 独自の高度なUI/UXが必要な場合
PanResponder
やreact-native-reanimated
などを用いたカスタム実装を検討しますが、これはかなりの労力を要します。 - FlatListではないがスクロール可能なコンテンツの場合
ScrollView
とRefreshControl
の組み合わせが適しています。 - 標準的なプル・トゥ・リフレッシュで十分な場合
FlatList
のrefreshing
とonRefresh
プロパティを使用するのが最も効率的で簡単な方法です。