React Native開発者必見!FlatListヘッダーの完全ガイド - ListHeaderComponentから固定ヘッダーまで

2025-05-31

FlatList は、React Native で効率的にスクロール可能なリストを表示するためのコンポーネントです。大量のデータを扱う際に、画面に表示される部分のみをレンダリングすることでパフォーマンスを最適化する「仮想化 (virtualization)」の仕組みを持っています。

ListHeaderComponent は、この FlatList のプロパティの一つで、リストのアイテムの_一番上_に表示されるコンポーネントを指定するために使用します。

例えば、以下のような場合に活用できます。

  • 広告バナー: リストの冒頭に固定で表示される広告など。
  • タイトルや説明: リストのコンテンツ全体に関するタイトルや説明文を表示したい場合。
  • 検索バーやフィルター: リストの上部に検索入力欄やフィルターオプションを配置したい場合。

特徴

  • 固定ヘッダー(Sticky Header)にしたい場合: 通常、ListHeaderComponent はスクロールで隠れてしまいますが、特定の条件下でヘッダーを画面上部に固定したい場合は、FlatListstickyHeaderIndices プロパティを [0] に設定することで実現できます(ただし、これは ListHeaderComponentFlatList内の最初の「アイテム」として扱われるためです)。
  • 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: 0opacity: 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外側にヘッダーコンポーネントを配置し、FlatListcontentContainerStylepaddingTop を適用して、ヘッダーの高さ分のスペースを確保することです。
    <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 がありますが、alwayshandled を設定することで、キーボードが表示された状態でもヘッダー内の要素がタップ可能になることがあります。
  • KeyboardAvoidingView の使用
    FlatListKeyboardAvoidingView で囲み、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 という関数コンポーネントを作成し、FlatListListHeaderComponent プロパティに JSX の形で <SimpleHeader /> として渡しています。これにより、FlatList のデータがレンダリングされる前に、このヘッダーが表示されます。ヘッダーもリストの一部としてスクロールします。

例2: インタラクティブなヘッダー(検索バーとボタン)

ヘッダーにユーザーが操作できる要素(TextInputButton など)を配置する例です。

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 を使って検索テキストを管理し、TextInputButton を含んでいます。Button が押されたとき(または TextInputonSubmitEditing が発火したとき)、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 との関連、注意点)

よくある勘違い
ListHeaderComponentstickyHeaderIndices={[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 には contentContainerStylepaddingTop を設定し、固定ヘッダーの高さ分だけリストのコンテンツを下にずらしています。これにより、リストの最初のアイテムがヘッダーに隠れずに表示されます。 zIndex: 1 は、ヘッダーがリストのコンテンツの上に確実に表示されるようにするためです。



主に以下の2つのシナリオにおける代替案を説明します。

  1. ヘッダーをリストアイテムの一部として扱い、スクロール時に一緒に動かす場合
  2. ヘッダーを画面上部に固定し、リストとは独立してスクロールさせない場合

ヘッダーをリストアイテムの一部として扱う (data プロパティに含める)

ListHeaderComponent を使わずに、FlatListdata プロパティにヘッダーの情報を最初のアイテムとして含め、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のみ) も設定する必要がある。
  • FlatListcontentContainerStylepaddingTop を設定して、ヘッダーの高さ分のオフセットを与える必要がある。ヘッダーの高さが動的に変わる場合、その高さを 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,
  },
});

解説
FixedHeaderComponentFlatList とは別にレンダリングされ、position: 'absolute' スタイルを使って画面の最上部に固定されます。FlatListcontentContainerStylepaddingTop を設定することで、リストのコンテンツがこの固定ヘッダーの下から始まるように見えます。これにより、リストをスクロールしてもヘッダーは常に表示されたままになります。これが、ListHeaderComponent を使用せず、固定ヘッダーを実現する最も推奨される方法です。