5. React Native 中的滚动视图

3,300 阅读12分钟

ScrollView

属性

  • horizontal:是否为水平滚动方向,默认为false,即默认为垂直方向布局
  • bounces:同UIScrollView
  • bouncesZoom:进行缩放操作时是否支持弹簧效果,同UIScrollView
  • contentOffset:内容偏移量,同UIScrollView
  • pagingEnabled:同UIScrollView
  • scrollEnabled:同UIScrollView
  • zoomScale:当前内容缩放比率,同UIScrollView
  • maximumZoomScale:同UIScrollView
  • minimumZoomScale:同UIScrollView
  • automaticallyAdjustContentInsets:当scrollView处于导航控制器或TabBar控制器中时,是否自动调整内容边距,默认为true,同UIScrollView
  • contentInset:边缘区域留白,默认{top: 0, left: 0, bottom: 0, right: 0}
  • contentInsetAdjustmentBehavior:内容边距自动适应行为,枚举值,同UIScrollView(iOS11新增)
    • automatic:自动,会根据刘海屏安全边距或者scrollView处于导航控制器或TabBar控制器中时自动设置内容边距
    • scrollableAxes:当scrollView内容长度超出滚动轴线长度时,会为scrollView加上安全区域对应轴线上的边距
    • never:不自动调整内容边距
    • always:总是会加上安全区域边距
  • contentContainerStyle:内容区域视图样式,用于设置包裹scrollView子视图的容器视图样式,例如设置内容边距、圆角等
  • alwaysBounceVertical:scrollView垂直滚动时,是否总是支持垂直方向弹簧效果,即使内容区域长度小于scrollView对应轴线上的长度。需设置bounces为开启,同UIScrollView
  • alwaysBounceHorizontal:同UIScrollViewalwaysBounceVertical
  • keyboardDismissMode:枚举值,同UIScrollView
    • none:scrollView滚动时,不操作键盘
    • on-drag:scrollView被拖拽时隐藏键盘
    • interactive:scrollView向下滚动时会隐藏键盘,向上滚动时不会
  • keyboardShouldPersistTaps:scrollView被点击时键盘是否需要被保留,枚举类型
    • never:默认,当点击键盘响应者以外的区域时收起键盘
    • always:总是保留键盘,此时scrollView将不再接收到点击事件,而其子组件可以
    • handled:当点击事件被scrollView的子组件处理时,键盘不收起。
  • indicatorStyle:进度条指示器样式,枚举类型:default(同black), black, white
  • showsHorizontalScrollIndicator:同UIScrollView
  • showsVerticalScrollIndicator:同UIScrollView
  • scrollsToTop:用户点击状态栏是否滚动scrollView到顶部
  • scrollEventThrottle:滚动事件回调控制阀,用于控制滚动回调的触发频率。过高频率的执行scroll的回调会影响列表的滚动性能,此值越高,回调频率越低。1~16之间无区别。如果设置为0,则一次滚动只会回调一次。
  • removeClippedSubviews:是否移除滚出屏幕的子组件,默认为true。开启可以提升长列表的性能
  • refreshControl:刷新控件
  • pinchGestureEnabled:是否支持捏合手势(用户缩放)
  • endFillColor:某些情况下,scrollView的contentSize会比实际内容的尺寸要大,此时,可通过此属性用颜色进行填充,省去整体设置背景色增加渲染成本

函数回调属性

  • onScroll:scrollView滚动回调,每帧最多回调一次。滚动事件具有如下参数格式: { nativeEvent: { contentInset: { bottom, left, right, top }, contentOffset: { x, y }, contentSize: { height, width }, layoutMeasurement: { height, width }, zoomScale } }
  • onScrollBeginDrag:用户即将开始拖拽的回调
  • onScrollEndDrag:用户结束拖拽的回调
  • onMomentumScrollBegin:用户拖拽手势结束,scrollView由于惯性开始滚动时的回调
  • onMomentumScrollEnd:scrollView因为惯性滚动结束时的回调
  • onContentSizeChange:scrollView内容尺寸改变时的回调,回传contentWidthcontentHeight两个参数
  • onScrollToTop:scrollView滚动到顶部后的回调

方法

  • flashScrollIndicators():短暂显示滚动进度条

    flashScrollIndicators();
    
  • scrollTo():滚动到指定位置

    scrollTo( options?: {x?: number, y?: number, animated?: boolean} );
    
  • scrollToEnd():滚动到底部

    scrollToEnd( [options]: {animated: boolean, duration: number} );
    

FlatList

FlatList类似于iOS中的UITableView,是一种高性能列表组件,具有如下特性:

  • 完全跨平台
  • 支持水平布局
  • 行组件显示或隐藏时可配置回调事件
  • 支持设置表头组件
  • 支持设置表尾组件
  • 支持行分隔组件
  • 支持下拉刷新
  • 支持滚动上拉加载
  • 支持滚动到指定行
  • 支持多列

注意点

  1. 通过设置extraData={state}以确保当state发生改变时,FlatList会重新渲染。因为FlatList是一个纯展示组件(PureComponent),如不设置此参数,则FlatList不会重新渲染任何子组件。
  2. 设置keyExtractor以告诉列表用哪种id作为itemReact key,以取代默认的key属性。
  3. 当内容被滚出渲染区域外时,item的内部状态不会被保存。请确保所有的状态数据都在item的data中被保留或者通过一些外部的存储手段(如Flux,Redux,Relay)进行了存储。
  4. PureComponent在其属性做***浅比较***未发生变化时不会进行重新渲染。因此,需要确保在renderItem函数中作为属性传递的参数在更新前后不是浅比较相等的,否则,UI将不会被更新。同样的情形适用于data和父组件的状态等。
  5. 为提升性能,列表内容是异步离屏渲染的。因此,快速滚动的时候可能会短暂看到空白内容。
  6. 默认情况下列表会为每个单元格提供一个key,可以通过keyExtractor自行指定。
JavaScript浅比较与深比较

浅比较:即比较符号===的比较结果,对于引用类型,比较引用的地址是否相等。对于PureComponent,如果浅比较的结果相等,即使改变了该引用对象内部的值,也不会重新渲染。

深比较:又称严格比较,不比较两个对象的地址,而是逐一遍历对象内部的每一条属性 逐一比较是否相等。

属性

属性完全继承ScrollView,除非是被嵌套于另一个同向滚动的FlatList中。

  • renderItem: 从数据源data获取一条数据,并将其渲染到list中。函数类型必需。参数说明:

    • item:从data获取即将被渲染的数据对象
    • indexitemdata数组中的索引
    • separators:对象类型,包含三个函数属性
      • highlight:函数,可配置高亮时执行的操作
      • unhighlight:函数,可配置解除高亮时执行的操作
      • updateProps:函数,上面两个函数不满足需求时,可以用这个
        • select:枚举,enum('leading', 'trailing')
        • newProps:对象
    renderItem({item, index, separators});
    
  • data:List的数据,普通数组类型,必需

  • ItemSeparatorComponent:单元格分隔组件,可以设置为组件函数元素,非必需。会在每个单元格之间渲染,默认提供highlightedleadingItem属性。renderItem提供separators.highlight/unhighlight来更新highlighted状态,也可以通过separators.updateProps来添加自定义属性(什么👻东西没看懂)

  • ListEmptyComponent:列表数据为空时的占位组件,可以设置为组件函数元素,非必需

  • ListFooterComponent:列表组尾组件,可以设置为组件函数元素,非必需

  • ListFooterComponentStyle:表尾组件容器的样式,style类型,非必需。实际上,ListFooterComponent是被包裹在一个容器内的,其style与此处的style是两个独立的对象,不能混淆。此属性是设置容器的样式,而非实际的表尾组件样式

  • ListHeaderComponent:列表表头组件,可以设置为组件函数元素,非必需

  • ListHeaderComponentStyle:列表表头容器组件样式,同ListFooterComponentStyle

  • numColumns:列数,默认为1。只有在滚动方向为竖直时,才支持设置此属性。并且,所有的单元格必须要等高。

  • columnWrapperStyle:多列布局时,每列组件的容器样式。只有numColumns大于1时,才可以设置此属性。

  • extraData:当发生变化时需要列表进行刷新的属性。

  • getItemLayout:获取单元格尺寸的回调函数,类似于UITableView返回单元格行高的代理方法。如果能提前预知单元格的尺寸并在此方法中计算返回,可以省去系统自动计算内容高度的过程,对于大量内容的列表性能有很好的提升。相关函数:

    (data, index) => {length: number, offset: number, index: number}
    

    计算offset的时候如果列表有设置ItemSeparatorComponent,需要把分隔组件的高度也计算在内

  • initialNumToRender:首批渲染的单元格数量。数量需满足填满首屏,但不要太多。这批单元格会被一直保留以提升列表滚到顶部的性能。

  • initialScrollIndex:首次渲染需要滚动到的单元格索引位置,需和getItemLayout配套使用

  • inverted:倒置列表,反向渲染

  • keyExtractor:单元格key映射函数,提取key用于缓存或排序等,类型为

    (item: object, index: number) => string;
    

    此函数会默认在item中查找key属性,如未找到,则会采用index

  • onEndReached:当列表滚动到底部时的回调。此处的底部是结合列表的底部阈值共同定义的,并不一定是指列表的真实底部

  • onEndReachedThreshold:列表底部阈值,列表滚动时认为到达的底部与列表内容真实底部的距离与列表组件自身长度的倍率。举例,当设置此值为0.5时,列表内容滚到到距离内容底部的距离为列表组件长度的一半时,就会调用onEndReached方法,认为列表已滚动到了底部。通过设置此属性,可以在列表未到达底部时,提前加载更多列表数据,从而保证视觉上列表滚动的流畅性。

  • onRefresh:列表下拉刷新回调。如果提供了此回调函数,系统会自动为列表添加一个默认的下拉刷新组件。在此方法中必须正确地设置列表的刷新状态。

  • refreshingboolean类型,在等待刷新时,必须将此属性设置为true

方法

  • scrollToIndex:滚动列表到将指定单元格显示到viewPosition参数所指定的区域位置

    scrollToIndex(params);
    

    params为对象类型,相关参数:

    • animated:是否以动画方式执行滚动,默认为true
    • index:需要被滚动到指定位置的单元格索引,必传
    • viewOffset:对最终目标位置的修正偏移量,必需
    • viewPosition0代表将指定单元格滚动到顶部,0.5代表将其滚动到居中显示,1代表将其滚动到停留在底部
    •   	注:如果没有指定FlatList的getItemLayout回调,不可以滚动到渲染区域以外的索引
      
  • scrollToItem:滚动到指定元素对应的单元格(此方法会线性遍历数据源,应尽量使用scrollToIndex)。注意点同上

    scrollToItem(params);
    

    params参数说明:

    • animated:是否以动画方式执行,默认为true
    • item:需滚动到的单元格对应的数据源
    • viewPosition:同上
  • scrollToOffset:滚动到指定内容偏移位置,等同于ScrollViewscrollTo()

    scrollToOffset(params);
    

    params参数说明:

    • animated:同上
    • offset:偏移的像素点数

FlatList代码示例

<FlatList
  data={DATA}   // 设置数据源
  renderItem={({ item }) => (
    <Item
      id={item.id}
      title={item.title}
      selected={selected.get(item.id)}
      onSelect={onSelect}
    />
  )}
  ItemSeparatorComponent={() => <Text style={{ backgroundColor: 'green', paddingLeft: 30, color: "brown", lineHeight: 30 }}>This is a separator!</Text>}
  ListEmptyComponent={() => <EmptyDataView title="This is an empty data view!" />} /** 使用函数创建组件和通过定义类创建组件实际调用的形式完全一致 */
  ListFooterComponent={<ListComponentFooter footerTitle="I am a list footer!" />}
  ListFooterComponentStyle={{ /* backgroundColor: 'green', */ height: 60, paddingHorizontal: 16, justifyContent: 'center' }}  // 设置表尾组件容器的样式
  keyExtractor={item => item.id}  // cell索引key,如不实现,默认会在item中查找'key'属性,如找不到'key'属性,则使用'index'。此函数形式为: (item, index) => {}
  extraData={selected}
// getItemLayout={(data, index) => (
//   {length: 60, offset: (60 + 30) * index, index}  // 如果有添加单元格分隔组件,在此处注意计算要包含该组件的内容长度
// )}
// initialScrollIndex={3}
// inverted={true} // 倒置列表,反向渲染
  onEndReachedThreshold={0.35}
  onEndReached={info => {
    console.log(`List scrolling reached the end! ${info.distanceFromEnd}`);
  }}
  scrollEventThrottle={1000} // 0:一次滚动(从开始到结束)仅调用一次。此值越小,回调的频率越高,0~16以内无区别。
  onScroll={event => {
    console.log(event.nativeEvent.contentOffset);
  }}
  onMomentumScrollBegin={() => {
    console.log("ScrollView begins scrolling!");  // 用户滑动手势结束后如果存在惯性,scrollView开始加速时的回调
  }}
  onMomentumScrollEnd={() => {
    console.log("ScrollView just ends scrolling!");
  }}
  onScrollBeginDrag={() => {
    console.log("User begins dragging!");
  }}
  onScrollEndDrag={() => {
    console.log("User ends dragging!");
  }}
/>

SectionList

SectionList类似于UITableView的分组样式,除了拥有与FlatList同样的特性以外,还额外支持:

  • 支持设置分组组头
  • 支持设置分组组尾
  • 支持渲染多类型混合的数据结构

属性

FlatList的属性SectionList基本都支持,此处不再赘述,只列举不同的属性。

  • renderItem:同FlatList
  • sections:需要被实际渲染的数据源,类似于FlatListdata属性
  • renderSectionHeader:各分组组头视图组件,在iOS系统中会分组组头会自动悬停在scrollView顶部,类型为[(info: {section: SectionT}) => ?React.Element]
  • renderSectionFooter:各分组组尾视图组件,类型同组头
  • SectionSeparatorComponent:各组第一个单元格上和最后一个单元格后的分隔组件。组头与组尾各有一个,且会被分组的组头与组尾组件包起来。此组件可以响应高亮状态,具体见ItemSeparatorComponent
  • stickySectionHeadersEnabled:分组的组头是否悬停在scrollView顶部,默认为true。仅限iOS平台

方法

  • scrollToLocation:滚动列表到将指定组的指定索引单元格显示在viewPosition指定的区域位置。类似于FlatListscrollToIndex

    scrollToLocation(params);
    

    参数说明:

    • animated:同FlatList相应方法
    • itemIndex:单元格在该组内的索引
    • sectionIndex:单元格所在分组的索引
    • viewOffset:同FlatList相应方法的对应参数
    • viewPosition:同FlatList相应方法的对应参数

类型定义

Section:
相关属性:

name type desc
data array 此分组内需要渲染的数据
[key] string 分组的索引key,不指定将默认采用index
[renderItem] 函数 可以在数据源中提供此函数来覆盖list自带的Item渲染方法
[ItemSeparatorComponent] 函数 为当前分组提供独立单元格分隔组件实现,可以覆盖单元格分隔组件实现
[keyExtractor] 函数 为当前分组提供独立的key提取器,覆盖list自带的函数

RefreshControl

一个用于为ScrollViewListView添加下拉刷新功能的组件。

refreshing是一个受控属性,必须在下拉刷新组件的onRefresh函数中正确将此属性的状态设置为true,否则下拉刷新操作将会立即结束

属性

  • refreshing:组件是否需要展示正在刷新的状态,在onRefresh中必须设置为true
  • onRefresh:刷新回调函数
  • enabled:是否启用,默认为true
  • tintColor:指示器菊花的渲染色,仅iOS平台支持
  • title:下拉刷新时提示文案,仅iOS平台支持
  • titleColor:下拉刷新提示文案颜色
  • size:指示器菊花尺寸,仅Android
  • progressViewOffset:指示器偏移量,仅Android
  • progressBackgroundColor:指示器背景颜色,仅Android

SectionList & RefreshControl 示例代码

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

  const onRefresh = React.useCallback(() => {
    setRefreshing(true);
    wait(2000).then(() => setRefreshing(false));
  }, [refreshing]);

  return (
    <SafeAreaView style={styles.container}>
      <SectionList
        sections={DATA}
        keyExtractor={(item, index) => item + index}
        refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={'red'} title={"下拉刷新"} />}
        renderItem={({ item }) => <Item title={item} />}
        renderSectionHeader={({ section: { title } }) => (
          <Text style={styles.header}>{title}</Text>
        )}
        renderSectionFooter={({ section: { title } }) => (
          <Text style={{ color: 'red', textAlign: 'center' }}>{title}</Text>
        )}
        SectionSeparatorComponent={() => <SectionSeparator />}
        stickySectionHeadersEnabled={false}
      />
    </SafeAreaView>
  );
}