React Native从入门到实战--React Native布局知识详解、高性能列表组件FlatList&VirtualizedList详解

React Native布局知识详解:

接着https://www.cnblogs.com/webor2006/p/14609259.html继续往下学习,这次主要是学习RN中布局的一些基础,同样是借助https://www.devio.org/2020/08/09/React-Native-Layout/大佬的文章来整体过一遍。学完它之后,对于Android架构的RN的混编大法就可以继续了:

想想还是蛮刺激的,加油~~

前言:

在React Native中布局采用的是FleBox(弹性框)进行布局【俗称的flex布局】。FlexBox提供了在不同尺寸设备上都能保持一致的布局方式。FlexBox是CSS3弹性框布局规范,目前还处于最终征求意见稿 (Last Call Working Draft)阶段,并不是所有的浏览器都支持Flexbox。但大家在做React Native开发时大可不必担心FlexBox的兼容性问题,因为既然React Native选择用FlexBox布局,那么React Native对FlexBox的支持自然会做的很好。

宽和高:

在学习FlexBox之前首先要清楚一个概念“宽和高”。一个组件的高度和宽度决定了它在屏幕上的尺寸,也就是大小。 

像素无关:

在React Native中尺寸是没有单位的,它代表了设备独立像素。

<View style={ {width:100,height:100,margin:40,backgroundColor:'gray'}}>
        <Text style={ {fontSize:16,margin:20}}>尺寸</Text>
</View>

上述代码中很显然对于具体的大小是直接定义成了一个数字,没加任何单位:

那对于移动端的小伙伴定会有这么一个疑问:这么写不会有屏幕适配的问题么?其实不用担心,在RN中的单位都是跟像素无关的,言外之意就是说:运行在Android上时,View的长和宽被解释成:100dp 100dp单位是dp,字体被解释成16sp 单位是sp,运行在iOS上时尺寸单位被解释成了pt,这些单位确保了布局在任何不同dpi的手机屏幕上显示不会发生改变;

和而不同:

值得一提的是,React Native中的FlexBox 和Web CSSS上FlexBox工作方式是一样的。但有些地方还是有些出入的,如:

React Native中的FlexBox 和Web CSSS上FlexBox的不同之处:

  • flexDirection: React Native中默认为flexDirection:'column',在Web CSS中默认为flex-direction:'row'
    也就是说对于RN中的flex布局的方向默认是列排列,而对于Web CSS中而言是行排列。
  • alignItems: React Native中默认为alignItems:'stretch',在Web CSS中默认align-items:'flex-start'
  • flex: 相比Web CSS的flex接受多参数,如:flex: 2 2 10%;,但在 React Native中flex只接受一个参数
  • RN中的FlexBox不支持属性:align-content,flex-basis,order,flex-basis,flex-flow,flex-grow,flex-shrink

以上是React Native中的FlexBox 和Web CSSS上FlexBox的不同之处,记住这几点,你可以像在Web CSSS上使用FlexBox一样,在React Native中使用FlexBox。

Layout Props:

Flex in React Native:

以下属性是React Native所支持的Flex属性。

主轴和侧轴(横轴和竖轴):

在学习上述属性之前,让我们先了解一个概念:主轴和侧轴,如下图所示:

主轴即水平方向的轴线,可以理解成横轴,侧轴垂直于主轴,可以理解为竖轴。其中主轴的起点是在最左边,终点是在最右边;侧轴的起点是最顶部,终点是在最底部。

父视图属性(容器属性):

flexDirection:

flexDirection enum('row', 'column','row-reverse','column-reverse') flexDirection属性定义了父视图中的子元素沿横轴或侧轴方片的排列方式。记得默认情况下是列排列。

  • row: 从左向右依次排列
  • row-reverse: 从右向左依次排列
  • column(default): 默认的排列方式,从上向下排列
  • column-reverse: 从下向上排列

接下来咱们在代码中来使用一下,整体用法如下:

<View style={ {backgroundColor:"darkgray",marginTop:20}}>
    <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>1</Text>
  </View>
  <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>2</Text>
  </View>
  <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>3</Text>
  </View>
  <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>4</Text>
  </View>
  </View>

接下来回到代码中来演练一下,前提得先创建一个RN的工程,关于这块项目的创建可以参考https://www.cnblogs.com/webor2006/p/14555391.html

这里运行到Android手机上确保一切木有问题【注意,此时由于没有指定RN的版本,用的是0.64最新版本,它在运行在IOS上是有问题的,此时可以在创建工程时指定一下版本为0.62.2既可,具体原因可以参考https://www.cnblogs.com/webor2006/p/14555391.html,由于这里学习是用的Android真机,所以没指定版本也木关系,不过在之后项目实战时还是需要指定一下,因为想看到跨平台的效果定要能在IOS运行才行,这里多啰嗦一下。】,然后用webstorm打开工程:

然后新建文件,将上述测试代码拷进文件中:

import React, { Component } from "react";
import { Text, View } from "react-native";

export default class FlexBoxTest extends Component {
  render() {
    return (
      <View style={{backgroundColor: "darkgray", marginTop: 20 }}>
        <View style={{ width: 40, height: 40, backgroundColor: "darkcyan", margin: 5 }}>
          <Text style={{ fontSize: 16 }}>1</Text>
        </View>
        <View style={{ width: 40, height: 40, backgroundColor: "darkcyan", margin: 5 }}>
          <Text style={{ fontSize: 16 }}>2</Text>
        </View>
        <View style={{ width: 40, height: 40, backgroundColor: "darkcyan", margin: 5 }}>
          <Text style={{ fontSize: 16 }}>3</Text>
        </View>
        <View style={{ width: 40, height: 40, backgroundColor: "darkcyan", margin: 5 }}>
          <Text style={{ fontSize: 16 }}>4</Text>
        </View>
      </View>
    );
  }
}

然后在App.js中来导入咱们的这个测试的View:

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

import FlexBoxTest from './FlexBoxTest';

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      remove: false,
      size: 0,
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <FlexBoxTest />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
    marginTop: 50,
  },
});

此时重新使用“npx react-native run-android”运行一下,如果运行有问题可以将Metro窗口给关掉再执行命令运行一次,运行结果为:

可以看到默认flexDirection的排列就是按列来排的,接下来咱们修改一下这个属性,将其改为行排列:

保存,此时立马就可以看到效果,如下:

接下来可以反转一下:

此时就是按照从右到左的顺序进行行排列了,同样的对于列布局也可以反着来:

flexWrap:

flexWrap enum('wrap', 'nowrap') flexWrap属性定义了子元素在父视图内是否允许多行排列,默认为nowrap。

  • nowrap flex的元素只排列在一行上,可能导致溢出。
  • wrap flex的元素在一行排列不下时,就进行多行排列。

使用方式如下:

<View style={ {flexWrap:'wrap',flexDirection:'row',backgroundColor:"darkgray",marginTop:20}}>
···
</View>

下面回到代码中来使用一下,为了看到效果,这里将元素按行来排列,并且每个元素的宽度加大一点:

目前运行效果:

此时一行显示不下就溢出了,如果不想溢出,就可以设置另一个属性,如下:

此时被溢出的元素就自动换行了:

justifyContent:

justifyContent属性定义了浏览器如何分配顺着父容器主轴【也就是x轴】的弹性(flex)元素之间及其周围的空间,默认为flex-start。

  • flex-start(default) 从行首开始排列。每行第一个弹性元素与行首对齐,同时所有后续的弹性元素与前一个对齐。
  • flex-end 从行尾开始排列。每行最后一个弹性元素与行尾对齐,其他元素将与后一个对齐。
  • center 伸缩元素向每行中点排列。每行第一个元素到行首的距离将与每行最后一个元素到行尾的距离相同。
  • space-between 在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素与行首对齐,每行最后一个元素与行尾对齐。
  • space-around 在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素到行首的距离和每行最后一个元素到行尾的距离将会是相邻元素之间距离的一半。

它的使用方式如下:

<View  style={ {justifyContent:'center',flexDirection:'row',backgroundColor:"darkgray",marginTop:20}}>
···
</View>

下面回到代码中来演练一下,为了看到效果,将每个元素的宽度由100改为50:

默认就是flex-start,也就是行首元素与行对齐:

接下来设置一下"flex-end":

可以看到行尾的那个元素是与行尾对齐的,接下来再来设置一下“center”,

可以看到整个元素居中了,且:

接下来看一下“space-between”:

其中第一个元素与行首进行对齐,最后一个元素与行尾进行对齐,中间的元素间隔相等。

接下来看最后一个属性“space-around”:

 

其中行首和行尾元素的距离是中间元素间距的一半。目前这是行布局的情况,对于列布局也是类似的,这里就不多说了。

alignItems:

alignItems属性以与justify-content相同的方式在侧轴方向上将当前行上的弹性元素对齐,默认为stretch。

  • flex-start 元素向侧轴起点对齐。
  • flex-end 元素向侧轴终点对齐。
  • center 元素在侧轴居中。如果元素在侧轴上的高度高于其容器,那么在两个方向上溢出距离相同。
  • stretch 弹性元素被在侧轴方向被拉伸到与容器相同的高度或宽度。

使用方式:

<View          style={ {justifyContent:'center',flexDirection:'row',backgroundColor:"darkgray",marginTop:20}}>
···
</View>

为了看到效果,这里将整个视图的高度加大一下:

此时运行效果为:

其实也就是属性flex-start的效果,接下来改成“flex-end”看一下:

发现木有效果,原来跟这个属性冲突了:

将其去掉,再来看下效果:

接下来设置成“center”,

运行:

接下来再设置另一个属性“streth”,

发现木有效果。。这是因为咱们给子元素限定了高度了:

如果不限定高度此时就会进行拉伸处理,如下:

效果:

而如果是column的话,则不能限定宽度:

 

效果:

子视图属性:

接下来则是设置子视图的属性。 

alignSelf:

alignSelf属性定义了flex容器内被选中项目的对齐方式。注意:alignSelf 属性可重写灵活容器的 alignItems 属性。

  • auto(default) 元素继承了它的父容器的 align-items 属性。如果没有父容器则为 “stretch”。
  • stretch 元素被拉伸以适应容器。
  • center 元素位于容器的中心。
  • flex-start 元素位于容器的开头。
  • flex-end 元素位于容器的结尾。

使用方式如下:

<View style={ {alignSelf:'baseline',width:60,height:    20,backgroundColor:"darkcyan",margin:5}}>
   <Text style={ {fontSize:16}}>1</Text>
</View>
...

也就是子视图的这个属性是可以覆盖父视图的alignItmes属性的,比如目前咱们父视图是stretch,其中将第一个元素进行了拉伸了:

比如我们设置第二个元素:

 

此时效果为:

 

然后设置第三个元素居中:

此时就用这个属性来将父视图的alignItems属性进行了覆盖了。 

flex:

flex number flex 属性定义了一个可伸缩元素的能力,默认为0。

使用方式:

<View style={ {flexDirection:'row',height:40, backgroundColor:"darkgray",marginTop:20}}>
  <View style={ {flex:1,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>flex:1</Text>
  </View>
  <View style={ {flex:2,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>flex:2</Text>
  </View>
  <View style={ {flex:3,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>flex:3</Text>
  </View>
</View>

下面到工程中来使用一下,为了看到效果,咱们先还是将元素固定一下宽高,并去掉一些其它的属性:

此时将第二个元素指定flex:

其效果类似于Android中LinearLayout中的weight权重属性,下面再来设置一下:

其效果:

其他布局 in React Native:

以下属性是React Native所支持的除Flex以外的其它布局属性。

视图边框:

  • borderBottomWidth number 底部边框宽度
  • borderLeftWidth number 左边框宽度
  • borderRightWidth number 右边框宽度
  • borderTopWidth number 顶部边框宽度
  • borderWidth number 边框宽度
  • borderColor 边框颜色

比如咱们给这个程序的父视图指定一个边框:

尺寸:

  • width number
  • height number

外边距:

  • margin number 外边距
  • marginBottom number 下外边距
  • marginHorizontal number 左右外边距
  • marginLeft number 左外边距
  • marginRight number 右外边距
  • marginTop number 上外边距
  • marginVertical number 上下外边距 

内边距:

  • padding number 内边距
  • paddingBottom number 下内边距
  • paddingHorizontal number 左右内边距
  • paddingLeft number 做内边距
  • paddingRight number 右内边距
  • paddingTop number 上内边距
  • paddingVertical number 上下内边距

如果有前端或移动端的经验,对于pading和margin这俩的区别应该都很容易理解,这里就不具体说明了。

边缘:

  • left number 属性规定元素的左边缘。该属性定义了定位元素左外边距边界与其包含块左边界之间的偏移。
  • right number 属性规定元素的右边缘。该属性定义了定位元素右外边距边界与其包含块右边界之间的偏移
  • top number 属性规定元素的顶部边缘。该属性定义了一个定位元素的上外边距边界与其包含块上边界之间的偏移。
  • bottom number 属性规定元素的底部边缘。该属性定义了一个定位元素的下外边距边界与其包含块下边界之间的偏移。

定位(position):

position enum(‘absolute’, ‘relative’)属性设置元素的定位方式,为将要定位的元素定义定位规则。

  • absolute:生成绝对定位的元素,元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
  • relative:生成相对定位的元素,相对于其正常位置进行定位。因此,”left:20” 会向元素的 LEFT 位置添加 20 像素。

高性能列表组件FlatList&VirtualizedList详解:

前言:

在APP开发过程中,列表可谓是页面最重要的一种展现形式了,几乎每一个APP都离不了列表,接下来则依照大佬的在这篇文章https://www.devio.org/2019/05/19/flatlist/来学习在React Native中该如何实现列表【整体过一遍】,以及FlatList的原理和实用指南。

FlatList的由来?

在React Native0.43版本中引入了FlatList,SectionListVirtualizedList,其中VirtualizedList是FlatList 与 SectionList 的底层实现。先看一下它的样子:

其实对于RN来说,有一个ListView的列表组件,那为啥还要推出一个FlatList的呢?

经常使用ListView的同学都知道: ListView的性能是比较差的,尤其是当有大量的数据需要展示的时候,ListView对内存的占用是相当可观的、丢帧卡顿那是常有的事。

那为什么ListView对于大数据量的情况下性能会很差呢?

深入ListView的原理你会发现,ListView对列表中的Item是全量渲染的,并且没有复用机制,这就难以避免当让ListView渲染大数据量的时候会发生以下两个问题:

  • 第一次打开与切换Tab时会出现卡顿或白屏的情况:这是因为ListView对所有的Item都是全量渲染的,比如:ListView中有100条Item,只有等这100条Item都渲染完成,ListView中的内容才会展示,这就难以避免卡顿白屏的问题;
  • 滑动列表时会出现卡顿与不跟手:当因ListView中展示了大量数据的时候,滑动列表你会发现没有少量数据的时候的跟手与流畅,这是因为ListView为了渲染大量数据需要大量的内存和计算,这对手机资源是一个很大的消耗,尤其是在一些低端机上甚至会出现OOM;

ListView的这种性能问题一直困扰着React Native开发者。有能力的公司、团队都纷纷对ListView做优化,封装自己的列表组件,然对性能的提升并不大,所以现在急需一个高性能的列表组件,于是便有了设计FlatList的构想;可以联想到Android中的ListView和RecyclerView。

那FlatList都有哪些特性呢?

FlatList是基于VirtualizedList的,要说FlatList的特性还要从VirtualizedList说起。

VirtualizedList:

VirtualizedList 是FlatList 与 SectionList 的底层实现。Vritualization 通过维护一个有限的渲染窗口(其中包含可见的元素),并将渲染窗口之外的元素全部用合适的定长空白空间代替的方式,极大的改善了内存消耗以及在有大量数据情况下的使用性能。这个渲染窗口能响应滚动行为。当一个元素离可视区太远时,它就有一个较低优先级;否则就获得一个较高的优先级。渲染窗口通过这种方式逐步渲染其中的元素(在进行了任何交互之后),以尽量减少出现空白区域的可能性。对于整个的原理可以参考下面这个示意图:

特性:

VirtualizedList有以下特性:

  • 支持滚动加载(具体可以借助onEndReached的回调,做数据动态加载);
  • 支持下拉刷新(借助onRefresh / refreshing属性实现);
  • 支持可配置的可见性(VPV)回调(借助onViewableItemsChanged / viewabilityConfig实现)
  • 滑动方向增加对Horizontal(水平)方向的支持;
  • 更加智能的Item以及section separators支持;
  • 支持Multi-column(借助numColumns属性实现);
  • 添加scrollToEnd, scrollToIndex, 和 scrollToItem方法的支持;
  • 对 Flow更加友好;

性能:

VirtualizedList除了简化API之外,新的列表组件还具有显着的性能增强,主要的是对于任意数量的行(Item)的增加不会带着内存的增加。 它主要是通过虚拟元素也就是在渲染窗口之外的元素将会被从组件结构上卸载以达到回收内存目的。这样会带来一个问题,即内部组件状态不会被保留,因此请确保你跟踪组件本身以外的任何重要状态,例如, 在Relay或Redux或Flux store。

限制渲染窗口还可以减少React和本地平台的工作量,例如View遍历。 即使你渲染了最后的一百万个元素,用这些新的列表也不需要渲染所有的元素来完成遍历。比如:你可以使用scrollToIndex跳至中间位置,而无需过多渲染。

另外VirtualizedList还对调度进行了一些改进,这对应用程序的响应很有帮助。 在任何手势或动画或其他交互完成后,呈现在窗口边缘的Item不会被频繁的渲染,并且渲染优先级比较低。

高级使用【记录,待之后查阅】:

  • 与ListView不同的是,渲染窗口中的所有Item在任何props改变时都会重新渲染,这在通常情况下是比较好的,因为渲染窗口的Item数量是不变的,但是如果Item比较复杂的话,你应该应确保遵循React最佳性能实践,并在适当情况下使用React.PureComponent和/或shouldComponentUpdate来限制你的组件以及子组件的渲染次数,减少不必要的渲染以及递归渲染等。
  • 如果你不需要渲染就知道内容的高度的话,可以通过getItemLayout 属性来改善用户体验,这使得通过例如滚动到具体Item更平滑。比如使用 scrollToIndex滚动到指定的Item。
  • 如果你有另一种数据类型比如immutable的list, 那么使用VirtualizedList是个不错的选择. 它提供一个getItem属性来让你为任何给定的index返回item数据。

注意事项:

  • 当某行滑出渲染区域之外后,其内部状态将不会保留。请确保你在行组件以外的地方保留了数据。
  • 本组件继承自PureComponent而非通常的Component,这意味着如果其props在浅比较中是相等的,则不会重新渲染。所以请先检查你的renderItem函数所依赖的props数据(包括data属性以及可能用到的父组件的state),如果是一个引用类型(Object或者数组都是引用类型),则需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。(译注:这一段不了解的朋友建议先学习下js中的基本类型和引用类型。)
  • 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。这是为了优化不得不作出的妥协,而我们也在设法持续改进。
  • 默认情况下每行都需要提供一个不重复的key属性。你也可以提供一个keyExtractor函数来生成key。
  • 另外如果你有一些特殊的需求或用例,你也通过调整一些参数来实现。 例如,你可以使用windowSize来平衡内存使用情况与用户体验,使用maxToRenderPerBatch调整填充率与响应度,使用onEndReachedThreshold以控制何时发生滚动加载等等。 

React Native列表的未来规划:

  • 完成现有的迁移(最终弃用ListView)。
  • 实现一些看到或听到的好的功能。
  • 粘滞头部支持。
  • 更多的性能优化。
  • 支持具有状态的功能Item组件。

FlatList的特性:

了解完VirtualizedList之后,接下来就让我们来认识一下FlatList的一些特性吧:

高性能的且使用简单的列表组件,支持一些特性:

  • 完全跨平台;
  • 支持水平布局模式;
  • 行组件显示或隐藏时可配置回调事件;
  • 支持单独的头部组件;
  • 支持单独的尾部组件;
  • 支持自定义行间分隔线;
  • 支持下拉刷新;
  • 支持上拉加载;
  • 支持跳转到指定行(ScrollToIndex);

如果需要分组/类/区(section)的功能,请使用<SectionList>

简单使用:

<FlatList
  data={[{key: 'a'}, {key: 'b'}]}
  renderItem={({item}) => <Text>{item.key}</Text>} />

使用也比较简单,关于这块的使用在之后的综合案例中再来进行操练。 

注意事项:

FlatList组件实质是基于<VirtualizedList>组件的封装,因此除了<VirtualizedList>需要注意的事项之外还有下面这些需要注意的事项:

  • removeClippedSubviews属性目前是不必要的,而且可能会引起问题。如果你在某些场景碰到内容不渲染的情况(比如使用LayoutAnimation时),尝试设置removeClippedSubviews={false}。我们可能会在将来的版本中修改此属性的默认值。

属性:

1、data: ?Array<ItemT>:

为了简化起见,data属性目前只支持普通数组。如果需要使用其他特殊数据结构,例如immutable数组,请直接使用更底层的VirtualizedList组件。

2、renderItem: (info: {item: ItemT, index: number}) => ?React.Element<any>:

根据行数据data渲染每一行的组件。典型用法:

_renderItem = ({item}) => ( <TouchableOpacity onPress={() => this._onPress(item)}> <Text>{item.title}}</Text> </TouchableOpacity> ); ... <FlatList data={[{title: 'Title Text', key: 'item1'}]} renderItem={this._renderItem} />

除data外还有第二个参数index可供使用。

3、onRefresh?: ?() => void:

如果设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。

4、refreshing?: ?boolean:

在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。

5、horizontal?: ?boolean:

设置为true则变为水平布局模式。也就是用这个属性可以决定是水平滑动还是垂直滑动。

6、initialNumToRender: number:

指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素。

7、keyExtractor: (item: ItemT, index: number) => string:

此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为key值。若item.key也不存在,则使用数组下标。

8、ItemSeparatorComponent?: ?ReactClass<any>:

行与行之间的分隔线组件。不会出现在第一行之前和最后一行之后。

9、ListFooterComponent?: ?ReactClass<any>:

通过它设置尾部组件。

10、ListHeaderComponent?: ?ReactClass<any>:

通过它设置头部组件。

11、columnWrapperStyle?: StyleObj:

如果设置了多列布局(即将numColumns值设为大于1的整数),则可以额外指定此样式作用在每行容器上。

12、extraData?: any:

如果有除data以外的数据用在列表中(不论是用在renderItem还是Header或者Footer中),请在此属性中指定。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。

13、getItem?:

获取指定的Item;

14、getItemCount?:

用于获取总共有多少Item;

15、getItemLayout?: (data: ?Array<ItemT>, index: number) => {length: number, offset: number, index: number}:

getItemLayout是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。如果你的行高是固定的,getItemLayout用起来就既高效又简单,类似下面这样:

getItemLayout={(data, index) => ( {length: 行高, offset: 行高 * index, index} )}

注意如果你指定了SeparatorComponent,请把分隔线的尺寸也考虑到offset的计算之中。

16、legacyImplementation?: ?boolean

设置为true则使用旧的ListView的实现。

17、numColumns: number

多列布局只能在非水平模式下使用,即必须是horizontal={false}。此时组件内元素会从左到右从上到下按Z字形排列,类似启用了flexWrap的布局。组件内元素必须是等高的——暂时还无法支持瀑布流布局。

18、onEndReached?: ?(info: {distanceFromEnd: number}) => void

当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用,用它就可以实现上拉加载更多的效果。

19、onEndReachedThreshold?: ?number

决定当距离内容最底部还有多远时触发onEndReached回调。注意此参数是一个比值而非像素单位。比如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。

20、onViewableItemsChanged?: ?(info: {viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void

在可见行元素变化时调用。可见范围和变化频率等参数的配置请设置viewabilityconfig属性。

21、viewabilityConfig?: ViewabilityConfig

可参考ViewabilityHelper的源码来了解具体的配置。

方法:

1、scrollToEnd(params?: object):

滚动到底部。如果不设置getItemLayout属性的话,可能会比较卡。

2、scrollToIndex(params: object):

滚动到指定位置,如果不设置getItemLayout属性的话,可能会比较卡。

3、scrollToItem(params: object):

需要线性扫描数据 - 如果可能,请使用scrollToIndex。如果不设置getItemLayout属性的话只能滚动到当前渲染窗口的某个位置。

4、scrollToOffset(params: object):

滚动到列表中的特定内容像素偏移量。

5、recordInteraction():

这个用得少。 

复杂使用:

下面是一个较复杂的例子,其中演示了如何利用PureComponent来进一步优化性能和减少bug产生的可能:

class MyListItem extends React.PureComponent {
  _onPress = () => {
    this.props.onPressItem(this.props.id);
  };

  render() {
    retuReact Native (
      <SomeOtherWidget
        {...this.props}
        onPress={this._onPress}
      />     )
  }
}

class MyList extends React.PureComponent { 
  state = {selected: (new Map(): Map<string, boolean>)};

  _keyExtractor = (item, index) => item.id;

  _onPressItem = (id: string) => {
    // updater functions are preferred for transactional updates
    this.setState((state) => {
      // copy the map rather than modifying state.
      const selected = new Map(state.selected);
      selected.set(id, !selected.get(id)); // toggle
      retuReact Native {selected};
    });
  };

  _renderItem = ({item}) => (
    <MyListItem
      id={item.id}
      onPressItem={this._onPressItem}
      selected={!!this.state.selected.get(item.id)}
      title={item.title}
    />   );

  render() {
    retuReact Native (
      <FlatList
        data={this.props.data}
        extraData={this.state}
        keyExtractor={this._keyExtractor}
        renderItem={this._renderItem}
      />     );
  }
}
  • 对于MyListItem组件来说,其onPressItem属性使用箭头函数而非bind的方式进行绑定,使其不会在每次列表重新render时生成一个新的函数,从而保证了props的不变性(当然前提是 id、selected和title也没变),不会触发自身无谓的重新render。换句话说,如果你是用bind来绑定onPressItem,每次都会生成一个新的函数,导致props在===比较时返回false,从而触发自身的一次不必要的重新render。
  • 给FlatList指定extraData={this.state}属性,是为了保证state.selected变化时,能够正确触发FlatList的更新。如果不指定此属性,则FlatList不会触发更新,因为它是一个PureComponent,其props在===比较中没有变化则不会触发更新。
  • keyExtractor属性指定使用id作为列表每一项的key。

实例:上拉加载更多,下拉刷新,自定义刷新组件:

效果:

接下来则来操练一把,最终会实现这样的效果:

也就是支持上拉加载更多、下拉刷新,并且其加载的样式进行了相应的定制。

实现:

1、创建RN项目:

这里则指定一下版本,为了能在IOS上进行运行:

然后在IOS上运行一下:

2、使用StackNavigator搭建DEMO的入口框架:

由于之后还会有不同的组件的学习,为了进行一个统一管理,所以这里打算将入口都统一的管理一下,将各个DEMO的学习都集成到一个页面上,类似于:

这里先安装一个react-navigation组件, 需要使用该命令:

其中npm i --save的含义上网搜了一下https://blog.csdn.net/xufeiayang/article/details/89302038

运行结果:

(base) xiongweideMacBook-Pro:FasterListDemo xiongwei$ npm i --save react-navigation
npm WARN @typescript-eslint/eslint-plugin@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN @typescript-eslint/parser@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-react@7.12.4 requires a peer of eslint@^3.0.0 || ^4.0.0 || ^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-react-native@3.6.0 requires a peer of eslint@^3.17.0 || ^4 || ^5 but none is installed. You must install peer dependencies yourself.
npm WARN tsutils@3.21.0 requires a peer of typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta but none is installed. You must install peer dependencies yourself.

+ react-navigation@4.4.4
added 12 packages from 6 contributors and audited 1218 packages in 17.585s

49 packages are looking for funding
  run `npm fund` for details

found 3 low severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details

其中说需要使用“npm audit fix”来修复一下,所以运行一下:

(base) xiongweideMacBook-Pro:FasterListDemo xiongwei$ npm audit fix
npm WARN @typescript-eslint/eslint-plugin@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN @typescript-eslint/parser@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-react@7.12.4 requires a peer of eslint@^3.0.0 || ^4.0.0 || ^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-react-native@3.6.0 requires a peer of eslint@^3.17.0 || ^4 || ^5 but none is installed. You must install peer dependencies yourself.
npm WARN tsutils@3.21.0 requires a peer of typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta but none is installed. You must install peer dependencies yourself.
npm WARN react-native@0.64.0 requires a peer of react@17.0.1 but none is installed. You must install peer dependencies yourself.
npm WARN jscodeshift@0.11.0 requires a peer of @babel/preset-env@^7.1.6 but none is installed. You must install peer dependencies yourself.

+ react-native@0.64.0
added 80 packages from 123 contributors, removed 115 packages and updated 35 packages in 38.112s

47 packages are looking for funding
  run `npm fund` for details

fixed 1 of 3 vulnerabilities in 1218 scanned packages
  2 vulnerabilities required manual review and could not be updated

此时在工程这块就发现已经添加了相关的依赖了:

不过这里先用低一点的版本,高版本的使用方式完全变了,为了保持跟课程同步,改为:

然后再npm install一下,另外可能运行还是会出错,对于ios来说记得可以运行一下:“cd ios; pod install; cd ..”,通常是能解决了。

下面则先来改造DEMO中的index.js:

接下来改造如下:

关于import的语法可以参考https://www.cnblogs.com/webor2006/p/14609259.html

下面则来创建一个演练FlatList的页面,通过导航器来关联上:

此时回到index.js中就可以定义如下:

经过这样的改造之后,其入口默认就是到APP.js中,接下来则回到默认的App.js页面中,增加一个跳转到FlatListDemo页面的入口按钮:

import React, { Component } from "react";
import { Button, StyleSheet, View } from "react-native";

type Props = {};
export default class App extends Component<Props> {
  render() {
    const { navigation } = this.props;
    return (
      <View style={styles.container}>
        <Button
          title={"FlatListDemo"}
          onPress={() => {
            navigation.navigate("FlatListDemo");
          }}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF",
  },
});

其中按钮点击就可以通过navigation来进行页面的跳转了,此时运行,就可以看到跳DEMO的入口页面了:

3、初步定义FlatList组件:

接下来则回到FlatListDemo.js页面中来使用一下FlatList:

import React, { Component } from "react";
import { FlatList, StyleSheet, Text, View } from "react-native";

type Props = {};
const CITY_NAMES = ["北京", "上海", "广州", "深圳", "杭州", "苏州", "成都", "武汉", "郑州", "洛阳", "厦门", "青岛", "拉萨"];
export default class FlatListDemo extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <FlatList
          data={CITY_NAMES}
          renderItem={(data => this._renderItem(data))}
        />
      </View>
    );
  }

  _renderItem(data) {
    return <View>
      <Text>{data.item}</Text>
    </View>;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

其中FlatList两个基础属性为:

对于renderItem它的渲染将其封装在了另一个函数中:

此时运行看一下效果:

4、调整item的样式:

目前列表项显示有点丑陋,此时需要定义一下样式,在RN中是如何来定义样式的呢?如下:

然后在StyleSheet中进行相关的样式属性的定义:

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  item: {
    height: 200,
    backgroundColor: "#169",
    marginLeft: 15,
    marginRight: 15,
    marginBottom: 15,
    alignItems: "center",//布局对齐方式
    justifyContent: "center",//内容居中
  },
  text: {
    color: "white",
    fontSize: 20,
  },
});

其中上面标有注释的justifyContent还记得当时学习flex布局时所说的含义么,回忆一下:

 

 运行效果:

5、实现下拉刷新效果:

如之前理论所描述,实现下拉刷新需要使用这个属性:

下面定义一下:

其中这里有个警告:

 

这块则需要在构造中定义一下了,具体定义如下:

此时下拉效果就有了:

 

6、处理loadData():

这里为了模拟,使用一下定时机制,然后将数据进行倒序排列,达到下拉刷新变化的效果,具体如下:

此时看效果:

 

其中这里涉及到RN的语法:

其中let这样声明有好处是啥呢?回忆一下:

使用let定义的变量只有在块级作用域之下才能使用,出了块级作用域就无法访问了,让其变量定义的范围更加的有意义。 

7、自定义loading样式:

在实际项目中有可能会定义loading下拉刷新的样式,目前默认的样式可能不一定能满足产品的要求,此时就需要进行样式自定义了,此时就需要使用另一个属性了:

此时再运行:

8、实现上拉加载更多:

此时需要用到另外两个属性了:

下面来定义一下:

其中需要导一下包: 

运行看一下效果:

嗯,已经有了,不过样式有点丑陋,下面来调整一下loading的样式:

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  item: {
    height: 200,
    backgroundColor: "#169",
    marginLeft: 15,
    marginRight: 15,
    marginBottom: 15,
    alignItems: "center",//布局对齐方式
    justifyContent: "center",//内容居中
  },
  text: {
    color: "white",
    fontSize: 20,
  },
  indicatorContainer: {
    alignItems: "center"
  },
  indicator: {
    color: 'red',
    margin: 10
  }
});

其中更改一下loading的颜色为红色:

运行:

最后来实现上拉加载更多的逻辑了,这里一气呵成:

import React, { Component } from "react";
import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";

type Props = {};
const CITY_NAMES = ["北京", "上海", "广州", "深圳", "杭州", "苏州", "成都", "武汉", "郑州", "洛阳", "厦门", "青岛", "拉萨"];
export default class FlatListDemo extends Component<Props> {

  constructor(props) {
    super(props);
    this.state = {
      dataArray: CITY_NAMES,
      isLoading: false,
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <FlatList
          data={this.state.dataArray}
          renderItem={(data => this._renderItem(data))}
          // refreshing={this.state.isLoading}//这里只有一个默认的loading样式,如需自定义则不能使用它
          // onRefresh={() => {
          //     this.loadData();
          // }}
          refreshControl={
            <RefreshControl
              title="Loading..."
              colors={["red"]}
              refreshing={this.state.isLoading}
              onRefresh={() => this.loadData(true)}
              tintColor={"orange"}//ios的菊花样式需要使用这个属性,如果android的话属性是另一个
            />
          }
          ListFooterComponent={() => this.genIndicator()}
          onEndReached={() => {
            this.loadData()
          }}
        />
      </View>
    );
  }

  genIndicator() {
    return <View style={styles.indicatorContainer}>
      <ActivityIndicator//跟平台相关的loading控件
        style={styles.indicator}
        size="large"
        color={'red'}
        animating={true}
      />
      <Text>正在加载更多</Text>
    </View>;
  }

  _renderItem(data) {
    return <View style={styles.item}>
      <Text style={styles.text}>{data.item}</Text>
    </View>;
  }

  //refresh表示是下拉刷新
  loadData(refresh) {
    if (refresh) {
      this.setState({
        isLoading: true
      });
    }
    setTimeout(() => {
      let dataArray = [];
      if (refresh) {
        for (let i = this.state.dataArray.length - 1; i >= 0; i--) {
          dataArray.push(this.state.dataArray[i])
        }
      } else {
        dataArray = this.state.dataArray.concat(CITY_NAMES);
      }
      this.setState({
        dataArray: dataArray,
        isLoading: false
      });
    }, 2000);
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  item: {
    height: 200,
    backgroundColor: "#169",
    marginLeft: 15,
    marginRight: 15,
    marginBottom: 15,
    alignItems: "center",//布局对齐方式
    justifyContent: "center",//内容居中
  },
  text: {
    color: "white",
    fontSize: 20,
  },
  indicatorContainer: {
    alignItems: "center"
  },
  indicator: {
    color: 'red',
    margin: 10
  }
});

其中在loadData中区分是否是下拉刷新:

其中有个语法就是将数据追加到数组中使用它:

而在调用时也稍加改了一下:

其中在调用loadData()时有一个小细节需要注意,在js中对于函数定义的参数可以不传:

最后运行看一下整体的效果:

当然FlatList组件还有n多其它的属性供使用,待之后再慢慢挖掘。

posted on 2021-05-06 06:52  cexo  阅读(1457)  评论(0编辑  收藏  举报

导航