React Native開発者必見!FlatListヘッダーの完全ガイド - ListHeaderComponentから固定ヘッダーまで
FlatList
は、React Native で効率的にスクロール可能なリストを表示するためのコンポーネントです。大量のデータを扱う際に、画面に表示される部分のみをレンダリングすることでパフォーマンスを最適化する「仮想化 (virtualization)」の仕組みを持っています。
ListHeaderComponent
は、この FlatList
のプロパティの一つで、リストのアイテムの_一番上_に表示されるコンポーネントを指定するために使用します。
例えば、以下のような場合に活用できます。
- 広告バナー: リストの冒頭に固定で表示される広告など。
- タイトルや説明: リストのコンテンツ全体に関するタイトルや説明文を表示したい場合。
- 検索バーやフィルター: リストの上部に検索入力欄やフィルターオプションを配置したい場合。
特徴
- 固定ヘッダー(Sticky Header)にしたい場合: 通常、
ListHeaderComponent
はスクロールで隠れてしまいますが、特定の条件下でヘッダーを画面上部に固定したい場合は、FlatList
のstickyHeaderIndices
プロパティを[0]
に設定することで実現できます(ただし、これはListHeaderComponent
がFlatList
内の最初の「アイテム」として扱われるためです)。 - React要素またはコンポーネント:
ListHeaderComponent
には、直接JSX要素(例:<Text>ヘッダー</Text>
) を渡すことも、別のReactコンポーネント(例:<MyHeaderComponent />
)を渡すこともできます。関数を渡してレンダリングすることも可能ですが、パフォーマンス最適化のためには、安定した参照を持つコンポーネントやuseCallback
でラップされた関数を使用することが推奨されます。 - スクロールと一緒に動く:
ListHeaderComponent
として指定されたコンポーネントは、FlatList
の他のアイテムと同様に、リストをスクロールすると一緒に上下に移動します。
使用例
import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
const DATA = [
{ id: '1', title: '最初のアイテム' },
{ id: '2', title: '2番目のアイテム' },
{ id: '3', title: '3番目のアイテム' },
// ...さらに多くのアイテム
];
const MyListHeader = () => (
<View style={styles.headerContainer}>
<Text style={styles.headerText}>これはリストのヘッダーです!</Text>
<Text style={styles.headerSubtitle}>以下にアイテムが表示されます</Text>
</View>
);
const App = () => {
return (
<FlatList
data={DATA}
renderItem={({ item }) => <Text style={styles.item}>{item.title}</Text>}
keyExtractor={item => item.id}
ListHeaderComponent={<MyListHeader />} // ここでヘッダーコンポーネントを指定
// stickyHeaderIndices={[0]} // ヘッダーを固定したい場合はコメントを外す
/>
);
};
const styles = StyleSheet.create({
headerContainer: {
padding: 20,
backgroundColor: '#f0f0f0',
borderBottomWidth: 1,
borderBottomColor: '#ccc',
alignItems: 'center',
justifyContent: 'center',
},
headerText: {
fontSize: 24,
fontWeight: 'bold',
},
headerSubtitle: {
fontSize: 16,
color: '#666',
marginTop: 5,
},
item: {
padding: 20,
fontSize: 18,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
});
export default App;
ListHeaderComponent
は非常に便利ですが、いくつか注意すべき点や、意図しない挙動が発生することがあります。
ヘッダーが表示されない、または期待通りに表示されない
よくある原因
- 親子コンポーネントのレイアウトの問題
FlatList
を含む親コンポーネントのスタイルが、FlatList
自体の描画領域を制限している(例:flex: 0
や固定の小さな高さなど)。 - スタイリングの問題
ヘッダーコンポーネント自体のスタイル(例:height: 0
やopacity: 0
など)によって見えなくなっている。 - コンポーネントが正しく渡されていない
ListHeaderComponent
に渡すのは、レンダリング可能なReact要素(JSX)か、コンポーネントそのもの、あるいはコンポーネントを返す関数である必要があります。- 例:
ListHeaderComponent={<MyHeaderComponent />}
(OK) - 例:
ListHeaderComponent={MyHeaderComponent}
(OK) - 例:
ListHeaderComponent={() => <MyHeaderComponent />}
(OK) - 例:
ListHeaderComponent={MyHeaderComponent()}
(NG:MyHeaderComponent
が関数を返さない場合、すぐに実行されてしまい期待通りに動作しないことがあります。)
- 例:
トラブルシューティング
- 開発者ツール (React Native Debuggerなど) で要素のインスペクターを使い、ヘッダーコンポーネントがDOM/ビューツリー内に存在するか、またそのサイズや位置を確認してください。
FlatList
を囲むView
などのコンテナにflex: 1
を指定するなどして、FlatList
が十分な領域を確保しているか確認してください。- ヘッダーコンポーネントのスタイルを一時的に削除するか、背景色を付けてみて、どこに表示されるか確認してください。
ListHeaderComponent
に渡すコンポーネントが、単独でレンダリングしたときに正しく表示されるか確認してください。
ヘッダーがスクロールで隠れない、または固定されない(stickyHeaderIndices との混同)
よくある原因
- stickyHeaderIndices の誤用または誤解
stickyHeaderIndices
は、FlatList
のデータ配列内の特定のインデックスのアイテムを画面上部に固定するためのものです。ListHeaderComponent
は厳密にはデータ配列のアイテムではないため、そのままではstickyHeaderIndices
の対象にはなりません。ただし、一部の状況(ヘッダーがリストの最初の論理的なアイテムとして扱われる場合)では[0]
を指定することで固定できる場合があります。 - ListHeaderComponent の基本的な挙動の誤解
ListHeaderComponent
は通常、リストの他のアイテムと同様にスクロールします。画面上部に「固定」させたい場合は、追加の設定が必要です。
トラブルシューティング
- もし
stickyHeaderIndices={[0]}
でListHeaderComponent
を固定したい場合は、ListHeaderComponent
がデータ配列の最初の項目と同じように振る舞うように設定されているか確認してください。これは少し高度なテクニックであり、場合によっては複雑になることがあります。一般的には上記の「外に配置」する方法がシンプルです。 ListHeaderComponent
を固定したい場合、最も一般的な方法は、FlatList
の外側にヘッダーコンポーネントを配置し、FlatList
のcontentContainerStyle
にpaddingTop
を適用して、ヘッダーの高さ分のスペースを確保することです。<View style={{ flex: 1 }}> <MyFixedHeaderComponent /> {/* FlatList の外に配置 */} <FlatList data={DATA} renderItem={...} keyExtractor={...} // contentContainerStyle={{ paddingTop: fixedHeaderHeight }} // ヘッダーの高さに合わせて調整 /> </View>
ヘッダー内のインタラクション(ボタンクリックなど)が動作しない
よくある原因
- Z-index の問題
稀に、他の要素とのZ-indexの競合により、タップ可能領域が正しく認識されないことがあります。 - pointerEvents スタイルの問題
意図せずpointerEvents: 'none'
が設定されている場合、その要素はタップイベントに応答しません。 - 他のコンポーネントに隠されている
ヘッダーコンポーネントの上に透明な別のコンポーネントが重なっていて、タップイベントをブロックしている可能性があります。
トラブルシューティング
- 簡単な
console.log('ボタンクリック')
をボタンのonPress
ハンドラに入れて、イベントが発火しているか確認してください。 - ヘッダーコンポーネントとその子要素に
pointerEvents
スタイルが設定されていないか確認してください。 - 開発者ツールで要素の階層を確認し、ヘッダーコンポーネントの上に何か他の要素が重なっていないか確認してください。
パフォーマンスの問題(ヘッダーが複雑な場合)
よくある原因
- 不必要な再レンダリング
ヘッダーコンポーネントが、関係のない親コンポーネントの状態変更によって頻繁に再レンダリングされている。 - ヘッダーコンポーネントが非常に複雑
大量のレンダリングロジック、多数のネストされたビュー、重い計算などを含むヘッダーは、初回レンダリングやスクロール時にパフォーマンスに影響を与える可能性があります。
トラブルシューティング
- React Native Debugger のパフォーマンスタブ
再レンダリングの頻度やコンポーネントのレンダリング時間をプロファイリングし、パフォーマンスボトルネックを特定してください。 - ヘッダーの複雑さを軽減
可能であれば、ヘッダーのロジックや表示内容をシンプルに保つようにしてください。 - React.memo の利用
ヘッダーコンポーネントが親の再レンダリングによって不必要に再レンダリングされるのを防ぐために、React.memo
でラップすることを検討してください。const MyListHeader = React.memo(() => ( <View> <Text>これはヘッダーです</Text> </View> ));
キーボードとヘッダーの干渉
よくある原因
- ヘッダー内にテキスト入力がある場合
ListHeaderComponent
内にTextInput
があり、キーボードが表示されるとレイアウトがずれたり、ヘッダーがキーボードに隠れてしまったりすることがあります。
- ScrollView の keyboardShouldPersistTaps
FlatList
には直接keyboardShouldPersistTaps
がありますが、always
やhandled
を設定することで、キーボードが表示された状態でもヘッダー内の要素がタップ可能になることがあります。 - KeyboardAvoidingView の使用
FlatList
をKeyboardAvoidingView
で囲み、behavior
プロパティを適切に設定します(例:Platform.OS === 'ios' ? 'padding' : 'height'
)。
ListHeaderComponent
は、FlatList
のデータアイテムより上に表示されるカスタムコンポーネントを指定するために使われます。これを使うことで、リストのタイトル、検索バー、フィルターオプション、広告バナーなど、様々な要素をリストの冒頭に配置できます。
例1: シンプルなテキストヘッダー
最も基本的な例で、単にテキストを表示するヘッダーです。
import React from 'react';
import { FlatList, Text, View, StyleSheet, SafeAreaView } from 'react-native';
// サンプルデータ
const DATA = [
{ id: '1', title: '最初のアイテム' },
{ id: '2', title: '2番目のアイテム' },
{ id: '3', title: '3番目のアイテム' },
{ id: '4', title: '4番目のアイテム' },
{ id: '5', title: '5番目のアイテム' },
{ id: '6', title: '6番目のアイテム' },
{ id: '7', title: '7番目のアイテム' },
{ id: '8', title: '8番目のアイテム' },
{ id: '9', title: '9番目のアイテム' },
{ id: '10', title: '10番目のアイテム' },
];
// リストの各アイテムのレンダリング関数
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
// リストヘッダーコンポーネント
const SimpleHeader = () => (
<View style={styles.headerContainer}>
<Text style={styles.headerText}>私の素敵なリスト</Text>
<Text style={styles.headerSubtitle}>以下にアイテムが表示されます</Text>
</View>
);
export default function App() {
return (
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
// ここでListHeaderComponentを指定
ListHeaderComponent={<SimpleHeader />}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
headerContainer: {
padding: 20,
backgroundColor: '#e0e0e0',
borderBottomWidth: 1,
borderBottomColor: '#ccc',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 10, // アイテムとの間に少しスペース
},
headerText: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
headerSubtitle: {
fontSize: 16,
color: '#666',
marginTop: 5,
},
});
解説
SimpleHeader
という関数コンポーネントを作成し、FlatList
の ListHeaderComponent
プロパティに JSX の形で <SimpleHeader />
として渡しています。これにより、FlatList
のデータがレンダリングされる前に、このヘッダーが表示されます。ヘッダーもリストの一部としてスクロールします。
例2: インタラクティブなヘッダー(検索バーとボタン)
ヘッダーにユーザーが操作できる要素(TextInput
や Button
など)を配置する例です。
import React, { useState } from 'react';
import { FlatList, Text, View, StyleSheet, TextInput, Button, Alert, SafeAreaView } from 'react-native';
const DATA = [ /* ... 例1と同じデータ ... */ ];
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
// インタラクティブなヘッダーコンポーネント
const InteractiveHeader = ({ onSearchPress }) => {
const [searchText, setSearchText] = useState('');
const handleSearch = () => {
onSearchPress(searchText); // 親コンポーネントに検索テキストを渡す
};
return (
<View style={styles.interactiveHeaderContainer}>
<TextInput
style={styles.searchInput}
placeholder="アイテムを検索..."
value={searchText}
onChangeText={setSearchText}
onSubmitEditing={handleSearch} // Enterキーでも検索
/>
<Button title="検索" onPress={handleSearch} />
<Text style={styles.headerNote}>リストの下にアイテムが表示されます。</Text>
</View>
);
};
export default function App() {
const handleSearchAction = (query) => {
Alert.alert('検索', `「${query}」で検索します`);
// ここで実際にデータをフィルタリングするロジックを実装
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
// interactiveHeader に props を渡す
ListHeaderComponent={<InteractiveHeader onSearchPress={handleSearchAction} />}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
// ... 例1のスタイルと共通部分 (container, item, title) ...
container: {
flex: 1,
backgroundColor: '#fff',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
interactiveHeaderContainer: {
padding: 15,
backgroundColor: '#e6f7ff', // 少し異なる背景色
borderBottomWidth: 1,
borderBottomColor: '#b3e0ff',
marginBottom: 10,
alignItems: 'center',
},
searchInput: {
height: 40,
width: '90%',
borderColor: '#add8e6',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 10,
backgroundColor: '#fff',
},
headerNote: {
marginTop: 10,
fontSize: 14,
color: '#336699',
},
});
解説
InteractiveHeader
コンポーネントは、useState
を使って検索テキストを管理し、TextInput
と Button
を含んでいます。Button
が押されたとき(または TextInput
で onSubmitEditing
が発火したとき)、onSearchPress
という props 経由で親コンポーネント(App
)に検索クエリを渡します。このように、ヘッダーコンポーネントは独立したロジックを持つことができ、親とのデータのやり取りも可能です。
例3: ヘッダーを React.memo
で最適化する
ヘッダーコンポーネントが複雑で、親コンポーネントの再レンダリング時に不必要にヘッダーも再レンダリングされるのを防ぐために、React.memo
を使用する例です。
import React, { useState, useCallback } from 'react';
import { FlatList, Text, View, StyleSheet, Button, SafeAreaView } from 'react-native';
const DATA = [ /* ... 例1と同じデータ ... */ ];
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
// ★ React.memo でラップされたヘッダーコンポーネント ★
const MemoizedHeader = React.memo(({ onHeaderButtonPress }) => {
console.log('MemoizedHeader がレンダリングされました'); // 再レンダリングを確認するためのログ
return (
<View style={styles.memoizedHeaderContainer}>
<Text style={styles.memoizedHeaderText}>最適化されたヘッダー</Text>
<Button title="ヘッダーのボタン" onPress={onHeaderButtonPress} />
</View>
);
});
export default function App() {
const [count, setCount] = useState(0);
// onHeaderButtonPress は useCallback でメモ化する
const handleHeaderButtonPress = useCallback(() => {
Alert.alert('ボタン', 'ヘッダーのボタンが押されました!');
}, []); // 依存配列が空なので、一度だけ作成される
return (
<SafeAreaView style={styles.container}>
<Button title={`親のカウント: ${count} を増やす`} onPress={() => setCount(prev => prev + 1)} />
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
// MemoizedHeader を渡す
ListHeaderComponent={<MemoizedHeader onHeaderButtonPress={handleHeaderButtonPress} />}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
// ... 例1のスタイルと共通部分 (container, item, title) ...
container: {
flex: 1,
backgroundColor: '#fff',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
memoizedHeaderContainer: {
padding: 20,
backgroundColor: '#d1e7dd', // 異なる背景色
borderBottomWidth: 1,
borderBottomColor: '#a3c2b1',
alignItems: 'center',
marginBottom: 10,
},
memoizedHeaderText: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
},
});
解説
MemoizedHeader
コンポーネントを React.memo
でラップしています。これにより、MemoizedHeader
に渡される props が変更されない限り、App
コンポーネントが再レンダリングされても MemoizedHeader
は再レンダリングされません。
App
コンポーネントの「親のカウントを増やす」ボタンを押すと、App
は再レンダリングされますが、MemoizedHeader
のログは一度しか表示されないはずです(初期レンダリング時のみ)。
重要: MemoizedHeader
に渡す onHeaderButtonPress
関数は、useCallback
を使ってメモ化されています。useCallback
を使わないと、App
が再レンダリングされるたびに onHeaderButtonPress
関数が新しい参照を持つことになり、MemoizedHeader
が不必要に再レンダリングされてしまうため、React.memo
の効果が薄れます。
例4: 固定ヘッダー(stickyHeaderIndices
との関連、注意点)
よくある勘違い
ListHeaderComponent
を stickyHeaderIndices={[0]}
で固定しようとすると、一部の環境や特定の条件(ヘッダーがリストの最初の論理的な「セクション」として扱われる場合など)で動作することがありますが、これは意図された主な使用方法ではありませんし、常に安定して動作するわけではありません。
推奨される固定ヘッダーの実装
ListHeaderComponent
を使わずに、FlatList
の外側にヘッダーを配置する方法が一般的で安定しています。
import React from 'react';
import { FlatList, Text, View, StyleSheet, SafeAreaView, Dimensions } from 'react-native';
const DATA = [ /* ... 例1と同じデータ ... */ ];
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
// 固定されるヘッダーコンポーネント (FlatListの外に配置)
const FixedHeader = () => (
<View style={styles.fixedHeaderContainer}>
<Text style={styles.fixedHeaderText}>これは固定ヘッダーです!</Text>
</View>
);
const HEADER_HEIGHT = 80; // ヘッダーの高さに合わせて調整
export default function App() {
return (
<SafeAreaView style={styles.container}>
{/* ★ FlatList の外にヘッダーを配置 ★ */}
<FixedHeader />
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
// contentContainerStyle を使ってヘッダー分のパディングを追加
// これにより、リストの最初のアイテムがヘッダーの下から始まるように見える
contentContainerStyle={{ paddingTop: HEADER_HEIGHT }}
// スクロールインジケーターもヘッダーの下から始まるように調整する場合
// scrollIndicatorInsets={{ top: HEADER_HEIGHT }} // iOSのみ効果あり
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
// position: 'relative' // FixedHeaderがposition: 'absolute'の場合に必要
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
fixedHeaderContainer: {
position: 'absolute', // 画面上部に固定
top: 0,
left: 0,
right: 0,
height: HEADER_HEIGHT, // 高さを指定
backgroundColor: '#87ceeb', // 固定ヘッダーの背景色
alignItems: 'center',
justifyContent: 'center',
zIndex: 1, // 他の要素より手前に表示
borderBottomWidth: 1,
borderBottomColor: '#4682b4',
},
fixedHeaderText: {
fontSize: 22,
fontWeight: 'bold',
color: '#fff',
},
});
解説
この例では、FixedHeader
コンポーネントを FlatList
の兄弟要素として配置し、position: 'absolute'
を使って画面上部に固定しています。
FlatList
には contentContainerStyle
の paddingTop
を設定し、固定ヘッダーの高さ分だけリストのコンテンツを下にずらしています。これにより、リストの最初のアイテムがヘッダーに隠れずに表示されます。
zIndex: 1
は、ヘッダーがリストのコンテンツの上に確実に表示されるようにするためです。
主に以下の2つのシナリオにおける代替案を説明します。
- ヘッダーをリストアイテムの一部として扱い、スクロール時に一緒に動かす場合
- ヘッダーを画面上部に固定し、リストとは独立してスクロールさせない場合
ヘッダーをリストアイテムの一部として扱う (data プロパティに含める)
ListHeaderComponent
を使わずに、FlatList
の data
プロパティにヘッダーの情報を最初のアイテムとして含め、renderItem
関数内で条件分岐してヘッダーをレンダリングする方法です。
メリット
stickyHeaderIndices
を使ってヘッダーを固定したい場合に、より直接的に制御できる可能性がある(ただし、この用途でのstickyHeaderIndices
は限定的です)。FlatList
のデータ構造の一部としてヘッダーを管理できる。
デメリット
keyExtractor
のロジックもヘッダーのキーを考慮する必要がある。renderItem
内でヘッダーと通常のアイテムのレンダリングを条件分岐させるロジックが必要になるため、コードが少し複雑になる。data
配列にヘッダーのための特別なエントリが必要になる。
コード例
import React from 'react';
import { FlatList, Text, View, StyleSheet, SafeAreaView } from 'react-native';
// 通常のリストデータ
const ITEMS_DATA = [
{ id: 'item_1', title: '最初のアイテム' },
{ id: 'item_2', title: '2番目のアイテム' },
{ id: 'item_3', title: '3番目のアイテム' },
// ...さらに多くのアイテム
];
// ヘッダー情報と通常のアイテム情報を組み合わせたデータ
const COMBINED_DATA = [
{ id: 'header', type: 'header', text: '特別なヘッダーです!' }, // ヘッダー用のエントリ
...ITEMS_DATA.map(item => ({ ...item, type: 'item' })), // 通常アイテムはtype: 'item'を追加
];
export default function App() {
const renderCombinedItem = ({ item }) => {
if (item.type === 'header') {
// ヘッダーの場合
return (
<View style={styles.combinedHeaderContainer}>
<Text style={styles.combinedHeaderText}>{item.text}</Text>
<Text style={styles.combinedHeaderSubText}>これはデータの一部としてレンダリングされています。</Text>
</View>
);
} else {
// 通常のアイテムの場合
return (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
}
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={COMBINED_DATA}
renderItem={renderCombinedItem}
keyExtractor={item => item.id}
// ヘッダーを固定したい場合にstickyHeaderIndicesを使用 (ただし、data配列の最初の要素であることが前提)
// stickyHeaderIndices={[0]}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
combinedHeaderContainer: {
padding: 20,
backgroundColor: '#d6e9c2', // 異なる背景色
borderBottomWidth: 1,
borderBottomColor: '#a7d97b',
alignItems: 'center',
marginBottom: 10,
},
combinedHeaderText: {
fontSize: 22,
fontWeight: 'bold',
color: '#4f8a10',
},
combinedHeaderSubText: {
fontSize: 14,
color: '#4f8a10',
marginTop: 5,
},
});
解説
COMBINED_DATA
という配列を作成し、その中に type: 'header'
を持つオブジェクトを最初の要素として追加しています。renderCombinedItem
関数内で item.type
をチェックし、それが 'header'
であればヘッダー用のUIを、そうでなければ通常のアイテムのUIをレンダリングしています。
この方法では、FlatList
がリストのデータ全体を管理するため、ListHeaderComponent
と同じくヘッダーもリストと一緒にスクロールします。
ヘッダーを画面上部に固定する (FlatList の外に配置)
これが最も一般的な「固定ヘッダー」の実装方法であり、ListHeaderComponent
の代替というよりも、ListHeaderComponent
では実現できない(または推奨されない)機能を実現するための方法です。
メリット
FlatList
の描画ロジックに影響を与えないため、パフォーマンス上の懸念が少ない。- ヘッダーとリストが独立したコンポーネントとして明確に分離され、管理しやすい。
- ヘッダーが常に画面上部に表示されるため、ユーザーエクスペリエンスが向上する。
デメリット
- スクロールインジケーター(スクロールバー)もヘッダーの下から始まるように調整したい場合は、
scrollIndicatorInsets
(iOSのみ) も設定する必要がある。 FlatList
のcontentContainerStyle
にpaddingTop
を設定して、ヘッダーの高さ分のオフセットを与える必要がある。ヘッダーの高さが動的に変わる場合、その高さをFlatList
に伝える追加のロジックが必要になる。
コード例
import React from 'react';
import { FlatList, Text, View, StyleSheet, SafeAreaView, Dimensions } from 'react-native';
const DATA = [ /* ... 例1と同じデータ ... */ ];
const FIXED_HEADER_HEIGHT = 80; // 固定ヘッダーの高さ
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
// 固定されるヘッダーコンポーネント
const FixedHeaderComponent = () => (
<View style={styles.fixedHeaderContainer}>
<Text style={styles.fixedHeaderText}>これは固定ヘッダーです!</Text>
<Text style={styles.fixedHeaderSubText}>リストはヘッダーの下をスクロールします。</Text>
</View>
);
export default function App() {
return (
<SafeAreaView style={styles.fixedHeaderMainContainer}>
{/* ★ 固定ヘッダーはFlatListの外に配置 ★ */}
<FixedHeaderComponent />
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
// ヘッダーの高さ分、コンテンツを下にずらす
contentContainerStyle={{ paddingTop: FIXED_HEADER_HEIGHT }}
// スクロールインジケーターもヘッダーの下から始まるようにする(iOSのみ)
scrollIndicatorInsets={{ top: FIXED_HEADER_HEIGHT }}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
fixedHeaderMainContainer: {
flex: 1,
backgroundColor: '#fff',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
fixedHeaderContainer: {
position: 'absolute', // 画面上部に固定
top: 0,
left: 0,
right: 0,
height: FIXED_HEADER_HEIGHT, // ヘッダーの高さ
backgroundColor: '#87ceeb', // 固定ヘッダーの背景色
alignItems: 'center',
justifyContent: 'center',
zIndex: 1, // 他の要素より手前に表示されるように
borderBottomWidth: 1,
borderBottomColor: '#4682b4',
},
fixedHeaderText: {
fontSize: 22,
fontWeight: 'bold',
color: '#fff',
},
fixedHeaderSubText: {
fontSize: 14,
color: '#fff',
marginTop: 4,
},
});
解説
FixedHeaderComponent
は FlatList
とは別にレンダリングされ、position: 'absolute'
スタイルを使って画面の最上部に固定されます。FlatList
の contentContainerStyle
に paddingTop
を設定することで、リストのコンテンツがこの固定ヘッダーの下から始まるように見えます。これにより、リストをスクロールしてもヘッダーは常に表示されたままになります。これが、ListHeaderComponent
を使用せず、固定ヘッダーを実現する最も推奨される方法です。