【React Native】ScrollView 和 FlatList 组件

发布于:2025-07-15 ⋅ 阅读:(20) ⋅ 点赞:(0)

ScrollView 组件

基础使用

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

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.content}>
        君不见黄河之水天上来,奔流到海不复回。
        君不见高堂明镜悲白发,朝如青丝暮成雪。
        人生得意须尽欢,莫使金樽空对月。
        天生我材必有用,千金散尽还复来。
        烹羊宰牛且为乐,会须一饮三百杯。
        岑夫子,丹丘生,将进酒,杯莫停。
        与君歌一曲,请君为我倾耳听。
        钟鼓馔玉不足贵,但愿长醉不愿醒。
        古来圣贤皆寂寞,惟有饮者留其名。
        陈王昔时宴平乐,斗酒十千恣欢谑。
        主人何为言少钱,径须沽取对君酌。
        五花马、千金裘,
        呼儿将出换美酒,与尔同销万古愁。
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  content: {
    fontSize: 60
  }
});

在这里插入图片描述

可以看到字体很大的情况下,文本溢出,但是不支持滚动(因为View组件不支持滚动)

但是此时将 View 组件改为 ScrollView 组件,会发现可以正常滚动:

<ScrollView style={styles.container}>
    // ...
</ScrollView>

ScrollView经常会作为一个页面里最大的容器,里面再嵌套View,和其他各种组件。

SafeAreaView 处理刘海屏问题

如果大家用的是iOS设备,会发现文字到最顶上去了,被iPhone的刘海给挡住了。如果用Android倒是没有这个问题。解决方案是使用SafeAreaView的组件。

ScrollView的外面,包上一层SafeAreaView,并且把ScrollView的样式,挪到SafeAreaView上来。

export default function App() {
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView>
        // ...
      </ScrollView>
    </SafeAreaView>
  );
};

RefreshControl 实现下拉刷新

ScrollView经常还会搭配RefreshControl组件来使用,这样可以实现下拉刷新。

import { useState } from "react";
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  SafeAreaView,
  RefreshControl,
} from "react-native";

export default function App() {
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = () => {
    setRefreshing(true);

    // 模拟重新读取接口
    console.log("开始读取接口了");
    setTimeout(() => {
      setRefreshing(false);
    }, 2000);
  };
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView
        refreshControl={
          <RefreshControl
            refreshing={refreshing}
            onRefresh={onRefresh}
            tintColor={"#1f99b0"}
          />
        }
      >
        <Text style={styles.content}>
          君不见黄河之水天上来,奔流到海不复回。
          君不见高堂明镜悲白发,朝如青丝暮成雪。
          人生得意须尽欢,莫使金樽空对月。 天生我材必有用,千金散尽还复来。
          烹羊宰牛且为乐,会须一饮三百杯。 岑夫子,丹丘生,将进酒,杯莫停。
          与君歌一曲,请君为我倾耳听。 钟鼓馔玉不足贵,但愿长醉不愿醒。
          古来圣贤皆寂寞,惟有饮者留其名。 陈王昔时宴平乐,斗酒十千恣欢谑。
          主人何为言少钱,径须沽取对君酌。 五花马、千金裘,
          呼儿将出换美酒,与尔同销万古愁。
        </Text>
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
  content: {
    fontSize: 60,
  },
});

配置了RefreshControl,并把这几个参数传进去:

  • refreshing,控制是否显示加载中指示器。
  • onRefresh,表示当用户下拉了,要执行什么函数。
  • 最后一个tintColor参数是加载中指示器的颜色,注意下,这个属性值只在iOS上有效,Android无效。

FlatList 组件

FlatList 组件用于处理长列表渲染。

以下这种直接渲染在数据量少的情况下可以正常使用,但是一旦数据量非常大,map 会遍历数组,然后一次性全部渲染出来。无论这个元素是否在屏幕的可见区域内,这样就会造成了大量的性能浪费。

import { Text, ScrollView, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native';

export default function App() {
  const data = [
    { "id": 1, "title": "静夜思" },
    { "id": 2, "title": "望庐山瀑布" },
    { "id": 3, "title": "早发白帝城" },
    { "id": 4, "title": "黄鹤楼送孟浩然之广陵" },
    { "id": 5, "title": "将进酒" },
    { "id": 6, "title": "行路难·其一" },
    { "id": 7, "title": "蜀道难" },
    { "id": 8, "title": "月下独酌·其一" },
    { "id": 9, "title": "赠汪伦" },
    { "id": 10, "title": "梦游天姥吟留别" },
    { "id": 11, "title": "宣州谢朓楼饯别校书叔云" },
    { "id": 12, "title": "送友人" },
    { "id": 13, "title": "登金陵凤凰台" },
    { "id": 14, "title": "清平调·其一" },
    { "id": 15, "title": "秋浦歌·白发三千丈" },
    { "id": 16, "title": "渡荆门送别" },
    { "id": 17, "title": "夜宿山寺" },
    { "id": 18, "title": "独坐敬亭山" },
    { "id": 19, "title": "关山月" },
    { "id": 20, "title": "子夜吴歌·秋歌" },
    { "id": 21, "title": "下终南山过斛斯山人宿置酒" },
    { "id": 22, "title": "月下独酌·其二" },
    { "id": 23, "title": "塞下曲六首·其一" },
    { "id": 24, "title": "玉阶怨" },
    { "id": 25, "title": "春夜洛城闻笛" },
    { "id": 26, "title": "越中览古" },
    { "id": 27, "title": "山中问答" },
    { "id": 28, "title": "清平调·其一" },
    { "id": 29, "title": "清平调·其二" },
    { "id": 30, "title": "清平调·其三" }
  ]

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView>
          {data.map((item) => (
            <Text key={item.id} style={styles.title}>
              {item.title}
            </Text>
          ))}
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  header: {
    textAlign: 'center',
    fontSize: 50,
    lineHeight: 60,
    fontWeight: 'bold',
  },
  footer: {
    textAlign: 'center',
    fontSize: 30,
    lineHeight: 60,
    color: '#999',
  },
  title: {
    textAlign: 'center',
    fontSize: 30,
    lineHeight: 60,
  }
});

更好的办法是使用FlatList,它是一个专门用来渲染长列表的组件,用它可以实现按需渲染。也就说只渲染当前屏幕可见的元素。当你滚动时,就动态加载新元素,回收不可见的元素。这样就只有比较小的性能开销了。

export default function App() {
  // ...

    return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={data}
        renderItem={({ item }) => <Text style={styles.title}>{item.title}</Text>}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
};
  • 它里面接受一个data参数,这就是要遍历的数据了。
  • renderItem,就是要遍历渲染出来的内容。
  • keyExtractor,与map里的key是一回事。
  • ListHeaderComponentListFooterComponent,可以在同一个页面里,顶部和底部渲染其他组件。

还有个特别要注意的事情,如果在FlatList外面加了一层ScrollView,应用会直接报错。

在这里插入图片描述

提示我们,不要把相同方向的虚拟列表,放到ScrollView里。这也就是说,ScrollView是上下滚动的,FlatList也是上下滚动的,这两个之间是有冲突的。所以这里有了FlatList了,它自己就可以实现滚动,就不要再额外加上ScrollView了。

但是如果FlatList不是上下滚动,而是横向的左右滚动,与ScrollView方向不同,就不会出现这个报错。

export default function App() {
  // ...

    return (
    <SafeAreaView style={styles.container}>
      <ScrollView>
        <FlatList
          data={data}
          renderItem={renderItem}
          keyExtractor={item => item.id}
          horizontal={true}
          ListHeaderComponent={<Text style={styles.header}>《唐诗 李白》</Text>}
          ListFooterComponent={<Text style={styles.footer}>没有更多了...</Text>}
        />
      </ScrollView>
    </SafeAreaView>
  );
};

添加上horizontal属性,设置成true,就会变成横向滚动了。

  • 如果要实现下拉刷新,FlatList也直接支持使用RefreshControl
  • onEndReached表示触底后,要执行什么函数。
  • onEndReachedThreshold,表示距离列表底部还有多少可见区域时触发。例如我们这里设置成0.1,这表示距离底部还剩10%的时候,就会触发要执行的函数。
import { useState } from "react";
import {
  Text,
  ScrollView,
  StyleSheet,
  FlatList,
  RefreshControl,
} from "react-native";
import { SafeAreaView } from "react-native";

export default function App() {
  const [refreshing, setRefreshing] = useState(false);
  const onRefresh = () => {
    setRefreshing(true);

    // 模拟重新读取接口
    console.log("开始读取接口了");
    setTimeout(() => {
      setRefreshing(false);
    }, 2000);
  };

  const onEndReached = () => {
    console.log('开始加载更多了');
  };
  
  const data = [
    { id: 1, title: "静夜思" },
    { id: 2, title: "望庐山瀑布" },
    { id: 3, title: "早发白帝城" },
    { id: 4, title: "黄鹤楼送孟浩然之广陵" },
    { id: 5, title: "将进酒" },
    { id: 6, title: "行路难·其一" },
    { id: 7, title: "蜀道难" },
    { id: 8, title: "月下独酌·其一" },
    { id: 9, title: "赠汪伦" },
    { id: 10, title: "梦游天姥吟留别" },
    { id: 11, title: "宣州谢朓楼饯别校书叔云" },
    { id: 12, title: "送友人" },
    { id: 13, title: "登金陵凤凰台" },
    { id: 14, title: "清平调·其一" },
    { id: 15, title: "秋浦歌·白发三千丈" },
    { id: 16, title: "渡荆门送别" },
    { id: 17, title: "夜宿山寺" },
    { id: 18, title: "独坐敬亭山" },
    { id: 19, title: "关山月" },
    { id: 20, title: "子夜吴歌·秋歌" },
    { id: 21, title: "下终南山过斛斯山人宿置酒" },
    { id: 22, title: "月下独酌·其二" },
    { id: 23, title: "塞下曲六首·其一" },
    { id: 24, title: "玉阶怨" },
    { id: 25, title: "春夜洛城闻笛" },
    { id: 26, title: "越中览古" },
    { id: 27, title: "山中问答" },
    { id: 28, title: "清平调·其一" },
    { id: 29, title: "清平调·其二" },
    { id: 30, title: "清平调·其三" },
  ];

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={data}
        renderItem={({ item }) => (
          <Text style={styles.title}>{item.title}</Text>
        )}
        keyExtractor={(item) => item.id}
        ListHeaderComponent={<Text style={styles.header}>《唐诗 李白》</Text>}
        ListFooterComponent={<Text style={styles.footer}>没有更多了...</Text>}
        refreshControl={
          <RefreshControl
            refreshing={refreshing}
            onRefresh={onRefresh}
            tintColor={"#1f99b0"}
          />
        }
        onEndReached={onEndReached}
        onEndReachedThreshold={0.1}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
  header: {
    textAlign: "center",
    fontSize: 50,
    lineHeight: 60,
    fontWeight: "bold",
  },
  footer: {
    textAlign: "center",
    fontSize: 30,
    lineHeight: 60,
    color: "#999",
  },
  title: {
    textAlign: "center",
    fontSize: 30,
    lineHeight: 60,
  },
});

网站公告

今日签到

点亮在社区的每一天
去签到