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
是一回事。- ListHeaderComponent和ListFooterComponent,可以在同一个页面里,顶部和底部渲染其他组件。
还有个特别要注意的事情,如果在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,
},
});