RN|系统组件之滚动组件及区别 📝

576 阅读13分钟

ScrollView

  • 支持固定子组件
  • 支持列表
  • 支持数组
// 固定子组件
<ScrollView style={styles.container}>
  <Text style={styles.txt}>1</Text>
  <Text style={styles.txt}>2</Text>
  <Text style={styles.txt}>3</Text>
  <Text style={styles.txt}>4</Text>
  <Text style={styles.txt}>5</Text>
</ScrollView>

// 数组
<ScrollView style={styles.container}>
  {arr.map((item, index) => {
    return (
      <Text key={index} style={styles.txt}>
        {item}
      </Text>
    );
  })}
</ScrollView>

// 列表
const buildListView = () => {
    const arr1 = [];

    for (let i = 0; i < 20; i++) {
      arr1.push(<Text key={i} style={styles.txt}>{`List Item ${i + 1}`}</Text>);
    }

    return arr1;
};
<ScrollView style={styles.container}>
  {buildListView()}
</ScrollView>

Props

contentContainerStyle

内容包裹样式,如设置列表内边距,样式同 View 组件

contentContainerStyle?: StyleProp<ViewStyle> | undefined;

contentContainerStyle 的样式将应用于滚动视图的内容容器,该容器包装了所有的子视图。示例:

return (
  <ScrollView contentContainerStyle={styles.contentContainer}>
  </ScrollView>
);
...
const styles = StyleSheet.create({
  contentContainer: {
    paddingVertical: 20
  }
});
  • contentContainerStyle 用来设置内容区域的样式,如 paddingalignItemsflexDirection 等,但 不能使用 margin

  • 如果需要设置组件外部的外边距,应该使用 style

keyboardDismissMode

确定在拖动操作中是否会隐藏键盘

keyboardDismissMode?: 'none' | 'interactive' | 'on-drag' | undefined;
  • 'none'(默认值)拖动操作不会隐藏键盘

  • 'onDrag' (Android)当开始拖动时,键盘会被隐藏

  • 'interactive' (iOS)当用户进行滚动操作时,键盘会随着滚动动作进行交互式地隐藏,即键盘会在手指拖动的过程中与视图同步移动。如果用户向上拖动内容,键盘会逐渐消失;如果用户向下拖动,键盘会重新出现

keyboardShouldPersistTaps

用于控制在触摸事件发生时,键盘是否应该保持显示状态

  • 'never' (默认值):点击 ScrollView 包裹的任何地方都会隐藏键盘。

  • 'always' :点击任何地方都不会隐藏键盘,键盘将始终保持显示状态。

  • 'handled' :只有在点击一个能够处理触摸事件的视图时(比如按钮或输入框)才会隐藏键盘,其他地方点击不会隐藏键盘。

使用场景:

  • 当你希望用户点击页面上的其他区域(例如,点击按钮或其他内容)时,键盘不被自动关闭时,使用 keyboardShouldPersistTaps="handled"keyboardShouldPersistTaps="always" 是很有用的。
  • 当用户在输入框之外的区域进行交互时,希望键盘仍然保持打开时,可以选择 always
  • 如果你想要更细致的控制,例如点击特定的区域(如按钮)才隐藏键盘,可以使用 handled
点击事件处理:
  • 'never'
    • 当键盘弹出时,点击按钮(或者其他有触摸事件的组件)时,键盘会隐藏,触摸事件不会被触发
    • 当键盘收起时,再点非触摸事件组件,则会触发触摸事件
<ScrollView keyboardShouldPersistTaps="never">
  <TextInput />
  <Button title="Click me" onPress={() => console.log("Button clicked")} />
</ScrollView>
  • 'always'
    • 点击事件会直接传递给子组件(例如按钮、文本框等),并且键盘保持可见状态。滚动组件本身不会响应点击事件,点击会被传递给子视图
    • 即使键盘是弹出的,点击按钮(或者其他有触摸事件的组件)时,按钮的点击事件仍然会被触发,键盘不会消失
<ScrollView keyboardShouldPersistTaps="always">
  <TextInput />
  <Button title="Click me" onPress={() => console.log("Button clicked")} />
</ScrollView>
  • 'handled'
    • 点击按钮(或者其他有触摸事件的组件)时,键盘不会隐藏,按钮的点击事件会被触发。如果点击背景区域,键盘会隐藏
<ScrollView keyboardShouldPersistTaps="handled">
  <TextInput />
  <Button title="Click me" onPress={() => console.log("Button clicked")} />
</ScrollView>
总结对比:

PS:外部区域是指 ScrollView 包裹的任何地方,除了输入控件(当前聚焦的文本输入框)外的区域

keyboardShouldPersistTaps键盘消失行为点击事件处理适用场景
'never'点击外部区域时,键盘隐藏点击事件不传递给子视图希望点击外部区域时隐藏键盘的场景
'always'键盘始终保持显示点击事件传递给子视图键盘始终显示,允许子视图处理点击事件
'handled'点击外部区域时,键盘隐藏点击事件由子视图处理点击子视图时键盘不消失,点击外部区域时消失
false'never' 相同'never' 相同已弃用,推荐使用 'never'
true'always' 相同'always' 相同已弃用,推荐使用 'always'

onMomentumScrollBegin

当滚动视图开始滚动时触发,且是松手的时候触发

  onMomentumScrollBegin?:
    | ((event: NativeSyntheticEvent<NativeScrollEvent>) => void)
    | undefined;

onMomentumScrollEnd

当滚动视图停止滚动时触发

  onMomentumScrollEnd?:
    | ((event: NativeSyntheticEvent<NativeScrollEvent>) => void)
    | undefined;

onScroll

  1. 是在滚动过程中每帧最多触发一次的事件,用于获取当前滚动的位置信息

  2. 其触发频率可以通过 scrollEventThrottle 属性控制(仅 iOS)

/**
 * 在滚动过程中每帧最多触发一次。
 * 事件的触发频率可以通过 scrollEventThrottle 属性进行控制。
 */
onScroll?:
  | ((event: NativeSyntheticEvent<NativeScrollEvent>) => void)
  | undefined;
<ScrollView
    onMomentumScrollBegin={() => {
      console.log('onMomentumScrollBegin');
    }}
    onMomentumScrollEnd={() => {
      console.log('onMomentumScrollEnd');
    }}
    onScroll={event => {
        console.log(event.nativeEvent.contentOffset);
    }}
 >
    <Text></Text>
    ...
</ScrollView>

image.png

horizontal

控制滚动方向是否水平方向滚动

/**
 * 如果为 true,滚动视图的子元素将水平排列在一行中,而不是垂直排列在一列中。默认值为 false。
 */
horizontal?: boolean | null | undefined

pagingEnabled

用于控制滚动视图的分页行为

/**
 * 如果为 true,滚动视图在滚动时会停在视图大小的倍数位置。这可以用于水平分页。默认值为 false。
 */
pagingEnabled?: boolean | undefined;
使用场景:
  • pagingEnabledtrue 时,滚动视图会在每次滚动时停在视图的大小倍数位置,类似于分页效果。这个特性常用于需要分页的场景,比如轮播图、分页滚动的内容等。
  • 默认值为 false,意味着滚动不会强制对齐到页面边界,用户可以自由地滚动。
<ScrollView
  horizontal={true}  // 水平滚动
  pagingEnabled={true}  // 启用分页效果
>
  <View style={{ width: 300, height: 200, backgroundColor: 'red' }} />
  <View style={{ width: 300, height: 200, backgroundColor: 'blue' }} />
  <View style={{ width: 300, height: 200, backgroundColor: 'gray' }} />
</ScrollView>

在上面的示例中,pagingEnabled={true} 会使得滚动视图每次滚动时停在每个 View 组件的边界,每个 View 会作为一页显示。

scrollEnabled

控制滚动视图是否可以滚动

/**
 * 如果为 false,内容不可滚动。默认值为 true。
 */
scrollEnabled?: boolean | undefined; // true

contentOffset

用于设置滚动视图的初始滚动位置,即内容从哪里开始显示

  • x: 水平偏移量,控制水平滚动的初始位置。
  • y: 垂直偏移量,控制垂直滚动的初始位置。
/**
 * 用于手动设置初始的滚动偏移量。
 * 默认值是 {x: 0, y: 0}。
 */
contentOffset?: PointProp | undefined; // 默认值是 {x: 0, y: 0}

示例:

<ScrollView contentOffset={{ x: 100, y: 200 }}>
  <Text>这里是滚动视图的内容</Text>
</ScrollView>

在上面的示例中,contentOffset={{ x: 100, y: 200 }} 会使得 ScrollView 在加载时,内容从水平 100 和垂直 200 的位置开始显示。

showsHorizontalScrollIndicator

控制是否显示水平滚动条,默认显示

/** * 如果为 true,显示水平滚动条。 */ 
showsHorizontalScrollIndicator?: boolean | undefined;

showsVerticalScrollIndicator

控制是否显示垂直滚动条,默认显示

/**
 * 如果为 true,显示垂直滚动条。
 */
showsVerticalScrollIndicator?: boolean | undefined;

stickyHeaderIndices

用于指定哪些子元素在滚动时应固定在屏幕顶部,通常用于实现“粘性头部”效果(例如,固定的标题)。

/**
 * 一个子元素索引的数组,决定哪些子元素在滚动时固定在屏幕顶部。
 * 例如,传入 `stickyHeaderIndices={[0]}` 会使第一个子元素固定在滚动视图的顶部。
 * 此属性不支持与 `horizontal={true}` 一起使用。
 */
stickyHeaderIndices?: number[] | undefined;
使用场景:
  • 当你希望某些元素在滚动过程中始终保持在屏幕顶部时(如导航栏、标题等),可以使用此属性。
  • 注意:此属性 不支持与 horizontal={true} 一起使用,意味着只适用于垂直滚动视图。

API

import React, {useRef} from 'react';

const scrollRef = useRef(null);

scrollRef.current.scrollTo({y: 300, animation: true});
scrollRef.current.scrollToEnd();

FlatList

  • 高效渲染:仅渲染可见项,提高性能,尤其在长列表中
  • 支持横向和纵向滚动:通过 horizontal 属性切换
  • 支持分页加载:通过 onEndReached 实现无限滚动
  • 下拉刷新功能:通过 refreshControl 实现
  • 头部和尾部组件:通过 ListHeaderComponentListFooterComponent 定制
  • 空数据提示:通过 ListEmptyComponent 显示空数据状态
  • 支持项的自定义键:通过 keyExtractor 提供唯一 key
  • 性能优化:使用 initialNumToRendergetItemLayout 等进行优化

Props

data

传递给 FlatList 的数据源,通常是一个数组

<FlatList data={data} />

renderItem

一个函数,用来渲染列表项,接收一个参数(item 数据),并返回要渲染的内容

<FlatList
  data={data}
  renderItem={({ item }) => <Text>{item.title}</Text>}
/>

keyExtractor

用于为每个项生成唯一的 key。通常通过返回数据项的某个唯一字段来实现

<FlatList
  data={data}
  keyExtractor={item => item.id.toString()}
/>

keyboardDismissMode

同 ScrollView

keyboardShouldPersistTaps

同 ScrollView

horizontal

同 ScrollView

onEndReached

在列表滚动到底部时触发的回调,用于加载更多数据

<FlatList
  data={data}
  onEndReached={loadMoreData}
  onEndReachedThreshold={0.5} // 设置触发的阈值
/>

onEndReachedThreshold

  • 用来设置触发 onEndReached 回调的阈值。

  • 该属性指定了距离列表底部的距离(以屏幕高度的比例或像素为单位)

  • 当滚动到这个距离时,onEndReached 会被触发

使用比例(屏幕高度的百分比)

<FlatList
  data={data}
  onEndReached={loadMoreData}
  onEndReachedThreshold={0.5} // 当滚动到距离底部 50% 的位置时触发
/>

使用像素值

<FlatList
  data={data}
  onEndReached={loadMoreData}
  onEndReachedThreshold={100} // 当滚动到距离底部 100 像素时触发
/>

onRefresh

触发下拉刷新时调用的函数

refreshing

表示列表是否处于刷新状态

  • true:表示正在刷新,通常显示一个刷新指示器(例如:一个旋转的图标)。

  • false:表示刷新已完成,刷新指示器消失。

const [isRefreshing, setIsRefreshing] = useState(false);

const handleRefresh = () => {
  setIsRefreshing(true);
  // 模拟网络请求
  setTimeout(() => {
    setIsRefreshing(false);
    // 更新数据
  }, 2000);
};

<FlatList
  data={data}
  onRefresh={handleRefresh}
  refreshing={isRefreshing}
/>

onScroll

监听滚动事件,通常用于跟踪滚动位置

<FlatList
  data={data}
  onScroll={(event) => console.log(event.nativeEvent.contentOffset.y)}
/>

getItemLayout

优化性能,当你知道列表项的高度或宽度时,可以直接指定,以便 FlatList 可以快速计算布局,而不必逐个测量

<FlatList
  data={data}
  getItemLayout={(data, index) => (
    { length: 50, offset: 50 * index, index }
  )}
/>

initialNumToRender

initialNumToRender 属性指定了 FlatList 初次渲染时显示的列表项数量。剩余的项会在用户滚动时按需加载(懒加载)。

ListEmptyComponent

FlatList 数据为空时渲染的组件

<FlatList
  data={data}
  ListEmptyComponent={<Text>No data available</Text>}
/>

ListHeaderComponent

渲染在列表顶部的组件

<FlatList
  data={data}
  ListHeaderComponent={<Text>Header</Text>}
/>

ListFooterComponent

渲染在列表底部的组件

<FlatList
  data={data}
  ListFooterComponent={<Text>Footer</Text>}
/>

viewabilityConfig

控制列表项的可见性检测配置

<FlatList
  data={data}
  viewabilityConfig={{
    itemVisiblePercentThreshold: 50 // 只有超过 50% 的项才会被视为可见
  }}
  onViewableItemsChanged={onViewableItemsChanged}
/>

onViewableItemsChanged

当可见项发生变化时触发的回调函数

onViewableItemsChanged={({ viewableItems, changed }) => {
  // viewableItems: 当前可见的项
  viewableItems.forEach(({ item, index }) => {
    console.log('Visible item:', item, 'at index', index);
  });

  // changed: 上次更新以来发生变化的项
  changed.forEach(({ item, index, isViewable }) => {
    console.log('Item changed:', item, 'at index', index, 'is now viewable:', isViewable);
  });
}}

numColumns

用于定义列表每行显示的列数,通常用于实现网格布局(grid layout)。

<FlatList
  data={data}
  renderItem={({ item }) => <View>{/* 每个项目的内容 */}</View>}
  numColumns={3}  // 每行显示 3 列
/>
  • 值类型numColumns 是一个数字,表示每行要显示的项数

  • 布局方向:当 numColumns 设置为大于 1 时,FlatList 的滚动方向通常会是垂直方向。如果你想要水平网格布局,则需要设置 horizontal={true}

  • 自定义项的宽度:如果你使用 numColumns,要确保每个项的宽度合适。通常,FlatList 会自动计算列的宽度,但你可以通过设置 style 来进一步调整每个项的布局

ItemSeparatorComponent

渲染每个项之间的分隔符。通常用于添加间隔或线条,帮助分隔列表项

<FlatList
  data={data}
  renderItem={({ item }) => <Text>{item}</Text>}
  ItemSeparatorComponent={() => (
    <View style={{ height: 1, backgroundColor: '#ddd' }} />
  )}
/>

API

scrollToIndex

将列表滚动到指定的索引位置

flatListRef.current.scrollToIndex({ index: 10 });

scrollToOffset

将列表滚动到指定的偏移量

flatListRef.current.scrollToOffset({ offset: 100 });

scrollToEnd

将列表滚动到最后一项

flatListRef.current.scrollToEnd();

setNativeProps

直接向 FlatList 组件的原生视图传递属性,通常用于性能优化

flatListRef.current.setNativeProps({ style: { backgroundColor: 'red' } });

SectionList

SectionList 是一个支持分组渲染的列表,适用于需要按类别、标题等方式分组显示数据的场景。

Props

sections

用于指定分组数据,每个分组包含一个 title 和该组的 data

const sections = [
  { title: 'Title 1', data: ['Item 1', 'Item 2', 'Item 3'] },
  { title: 'Title 2', data: ['Item 4', 'Item 5'] },
  { title: 'Title 3', data: ['Item 6'] },
];

renderItem

渲染每个项的内容,传递给它的数据项是 section.data 中的元素

renderItem={({ item }) => <Text>{item}</Text>}

renderSectionHeader

渲染每个分区的标题(即每个 section 的 title

renderSectionHeader={({ section }) => <Text>{section.title}</Text>}

stickySectionHeadersEnabled

设置是否启用粘性分区标题(即标题在滚动时固定在顶部)

默认值false

SectionList 实现一个分组列表:

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

const sections = [
  { title: 'Fruits', data: ['Apple', 'Banana', 'Orange', 'Mango', 'Pineapple', 'Grapes', 'Peach', 'Strawberry', 'Blueberry', 'Kiwi'] },
  { title: 'Vegetables', data: ['Carrot', 'Broccoli', 'Spinach', 'Cucumber', 'Tomato', 'Potato', 'Onion', 'Garlic', 'Lettuce', 'Cabbage'] },
  { title: 'Dairy', data: ['Milk', 'Cheese', 'Yogurt', 'Butter', 'Cream', 'Ice Cream'] },
  { title: 'Meat', data: ['Chicken', 'Beef', 'Pork', 'Lamb', 'Turkey'] },
  { title: 'Beverages', data: ['Tea', 'Coffee', 'Juice', 'Soda', 'Water', 'Wine', 'Beer'] },
  { title: 'Snacks', data: ['Chips', 'Cookies', 'Candy', 'Chocolate', 'Nuts', 'Popcorn'] },
];


const MySectionList = () => {
  return (
    <SectionList
      sections={sections}
      renderItem={({item}) => (
        <View style={styles.item}>
          <Text>{item}</Text>
        </View>
      )}
      renderSectionHeader={({section}) => (
        <View style={styles.header}>
          <Text style={styles.headerText}>{section.title}</Text>
        </View>
      )}
      keyExtractor={(item, index) => item + index}
      stickySectionHeadersEnabled={true}
      ItemSeparatorComponent={() => <View style={styles.separator} />}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 10,
    backgroundColor: '#f9f9f9',
  },
  header: {
    backgroundColor: '#6F4F1F',
    padding: 5,
  },
  headerText: {
    color: '#fff',
    fontWeight: 'bold',
  },
  separator: {
    height: 1,
    backgroundColor: '#ccc',
  },
});

export default MySectionList;

image.png

SectionList 有很多属性是跟 FlatList 一样的:

keyExtractor

同 FlatList

ListHeaderComponent

同 FlatList

ListFooterComponent

同 FlatList

onEndReached

同 FlatList

onEndReachedThreshold

同 FlatList

refreshing

同 FlatList

onRefresh

同 FlatList

ItemSeparatorComponent

同 FlatList

getItemLayout

同 FlatList

initialNumToRender

同 FlatList

horizontal

同 FlatList

onViewableItemsChanged

同 FlatList

keyboardDismissMode

同 FlatList

keyboardShouldPersistTaps

同 FlatList

RefreshControl

用于实现下拉刷新功能的组件,通常和 ScrollViewFlatListSectionList 一起使用。它能够在用户下拉列表时触发刷新事件,通常用于加载新的数据或刷新页面内容。

Props

refreshing (必填)

控制刷新指示器的显示和隐藏。如果值为 true,则显示刷新动画;

如果值为 false,则隐藏刷新动画。

onRefresh(必填)

当用户触发下拉刷新时调用的回调函数。通常在这个函数内会进行数据请求或状态更新操作。

tintColor

设置刷新指示器的颜色

使用 RefreshControlFlatList 配合实现下拉刷新功能:

import React, { useState } from 'react';
import { FlatList, Text, View, RefreshControl, StyleSheet } from 'react-native';

const MyFlatList = () => {
  const [data, setData] = useState(['Item 1', 'Item 2', 'Item 3']);
  const [isRefreshing, setIsRefreshing] = useState(false);

  const handleRefresh = () => {
    setIsRefreshing(true);
    // 模拟网络请求
    setTimeout(() => {
      // 假设数据已刷新
      setData(['Item 4', 'Item 5', 'Item 6']);
      setIsRefreshing(false);
    }, 2000); // 2秒后刷新完成
  };

  return (
    <FlatList
      data={data}
      renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
      keyExtractor={(item, index) => index.toString()}
      refreshControl={
        <RefreshControl
          refreshing={isRefreshing}
          onRefresh={handleRefresh}
          tintColor="#0000ff"
        />
      }
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
});

export default MyFlatList;

image.png

不同滚动组件的区别

ScrollView

特点:

  • 用于滚动容器,可以包含任何类型的子组件
  • 渲染所有子项:ScrollView 会一次性渲染所有子元素,适用于数据量较小的场景
  • 不支持虚拟化ScrollView 会将所有内容都渲染到内存中,不做懒加载,因此对于大数据列表不够高效

FlatList

特点:

  • 专门为长列表优化,支持高效的虚拟化(仅渲染当前视口内的项)。
  • 支持分页加载、懒加载等优化,大数据量时表现更好
  • 只支持一维数据列表,即一个简单的数组

SectionList

特点:
  • 用于渲染带有分组数据的列表
  • 支持每个分组的头部,通常用于展示带有分组的复杂数据(如按类别分的商品、按时间分的事件等)
  • 也支持虚拟化,因此对于大量数据的渲染效率较高

总结对比

特性ScrollViewFlatListSectionList
用途用于简单的滚动视图,包含多个组件用于展示一维列表(不支持分组)用于展示带有分组标题的数据
数据结构任意内容,适用于少量内容或固定内容一维数据列表(数组)分组数据(每个组包含一个 titledata 数组)
性能不支持虚拟化,适用于小数据量支持虚拟化,适用于大数据量支持虚拟化,适用于大数据量且需要分组的情况
支持的功能可以嵌套不同类型的子组件支持懒加载、分页加载、滚动到指定项等功能支持渲染分组数据和分组标题,适合展示分组列表
适用场景数据量少,页面布局较为简单长列表、大数据量的单一列表项需要分组显示的长列表,例如联系人、新闻类别等

选择依据:

  • 如果是简单的内容展示和小数据量,使用 ScrollView
  • 如果是大数据量的单一列表,选择 FlatList
  • 如果是大数据量并且需要分组显示,选择 SectionList