【React Native】ScrollView 和 FlatList 组件
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}><ScrollViewrefreshControl={<RefreshControlrefreshing={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}><FlatListdata={data}renderItem={({ item }) => <Text style={styles.title}>{item.title}</Text>}keyExtractor={item => item.id}/></SafeAreaView>);
};
- 它里面接受一个
data
参数,这就是要遍历的数据了。 renderItem
,就是要遍历渲染出来的内容。keyExtractor
,与map
里的key
是一回事。- ListHeaderComponent和ListFooterComponent,可以在同一个页面里,顶部和底部渲染其他组件。
还有个特别要注意的事情,如果在FlatList
外面加了一层ScrollView
,应用会直接报错。
提示我们,不要把相同方向的虚拟列表,放到
ScrollView
里。这也就是说,ScrollView
是上下滚动的,FlatList
也是上下滚动的,这两个之间是有冲突的。所以这里有了FlatList
了,它自己就可以实现滚动,就不要再额外加上ScrollView
了。
但是如果FlatList
不是上下滚动,而是横向的左右滚动,与ScrollView
方向不同,就不会出现这个报错。
export default function App() {// ...return (<SafeAreaView style={styles.container}><ScrollView><FlatListdata={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}><FlatListdata={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={<RefreshControlrefreshing={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,},
});