当前位置: 移动技术网 > IT编程>开发语言>JavaScript > ReactNative 之FlatList使用及踩坑封装总结

ReactNative 之FlatList使用及踩坑封装总结

2017年12月08日  | 移动技术网IT编程  | 我要评论

在rn中flatlist是一个高性能的列表组件,它是listview组件的升级版,性能方面有了很大的提升,当然也就建议大家在实现列表功能时使用flatlist,尽量不要使用listview,更不要使用scrollview。既然说到flatlist,那就先温习一下它支持的功能。

  1. 完全跨平台。
  2. 支持水平布局模式。
  3. 行组件显示或隐藏时可配置回调事件。
  4. 支持单独的头部组件。
  5. 支持单独的尾部组件。
  6. 支持自定义行间分隔线。
  7. 支持下拉刷新。
  8. 支持上拉加载。
  9. 支持跳转到指定行(scrolltoindex)。

今天的这篇文章不具体介绍如何使用,如果想看如何使用,可以参考我github https://github.com/xiehui999/helloreactnative的一些示例。今天的这篇文章主要介绍我使用过程中感觉比较大的坑,并对flatlist进行的二次封装。

接下来,我们先来一个简单的例子。我们文章也有这个例子开始探讨。

    <flatlist
      data={this.state.datalist} extradata={this.state}
      refreshing={this.state.isrefreshing}
      onrefresh={() => this._onrefresh()}
      keyextractor={(item, index) => item.id}
      itemseparatorcomponent={() => <view style={{
        height: 1,
        backgroundcolor: '#d6d6d6'
      }}/>}
      renderitem={this._renderitem}
      listemptycomponent={this.emptycomponent}/>
      
      
  //定义空布局
    emptycomponent = () => {
    return <view style={{
      height: '100%',
      alignitems: 'center',
      justifycontent: 'center',
    }}>
      <text style={{
        fontsize: 16
      }}>暂无数据下拉刷新</text>
    </view>
  }

在上面的代码,我们主要看一下listemptycomponent,它表示没有数据的时候填充的布局,一般情况我们会在中间显示显示一个提示信息,为了介绍方便就简单展示一个暂无数据下拉刷新。上面代码看起来是暂无数据居中显示,但是运行后,你傻眼了,暂无数据在最上面中间显示,此时高度100%并没有产生效果。当然你尝试使用flex:1,将view的高视图填充剩余全屏,不过依然没有效果。

那为什么设置了没有效果呢,既然好奇,我们就来去源码看一下究竟。源码路径在react-native-->libraries-->lists。列表的组件都该目录下。我们先去flatlist文件搜索关键词listemptycomponent,发现该组件并没有被使用,那就继续去render

 render() {
  if (this.props.legacyimplementation) {
   return (
    <metrolistview
     {...this.props}
     items={this.props.data}
     ref={this._captureref}
    />
   );
  } else {
   return (
    <virtualizedlist
     {...this.props}
     renderitem={this._renderitem}
     getitem={this._getitem}
     getitemcount={this._getitemcount}
     keyextractor={this._keyextractor}
     ref={this._captureref}
     onviewableitemschanged={
      this.props.onviewableitemschanged && this._onviewableitemschanged
     }
    />
   );
  }
 }

metrolistview(内部实行是scrollview)是旧的listview实现方式,virtualizedlist是新的性能比较好的实现。我们去该文件

  //省略部分代码
  const itemcount = this.props.getitemcount(data);
  if (itemcount > 0) {
    ....省略部分代码
  } else if (listemptycomponent) {
   const element = react.isvalidelement(listemptycomponent)
    ? listemptycomponent // $flowfixme
    : <listemptycomponent />;
   cells.push(
    /* $flowfixme(>=0.53.0 site=react_native_fb,react_native_oss) this
     * comment suppresses an error when upgrading flow's support for react.
     * to see the error delete this comment and run flow. */
    <view
     key="$empty"
     onlayout={this._onlayoutempty}
     style={inversionstyle}>
     {element}
    </view>,
   );
  }

再此处看到我们定义的listemptycomponent外面包了一层view,该view加了样式inversionstyle。

const inversionstyle = this.props.inverted
   ? this.props.horizontal
    ? styles.horizontallyinverted
    : styles.verticallyinverted
   : null;
   
样式:
verticallyinverted: {
  transform: [{scaley: -1}],
 },
 horizontallyinverted: {
  transform: [{scalex: -1}],
 },

上面的样式就是添加了一个动画,并没有设置高度,所以我们在listemptycomponent使用height:'100%'或者flex:1都没有效果,都没有撑起高度。

为了实现我们想要的效果,我们需要将height设置为具体的值。那么该值设置多大呢?你如果给flatlist设置一个样式,背景属性设置一个颜色,发现flatlist是默认有占满剩余屏的高度的(flex:1)。那么我们可以将listemptycomponent中view的高度设置为flatlist的高度,要获取flatlist的高度,我们可以通过onlayout获取。

代码调整:

//创建变量
fheight = 0;

    <flatlist
      data={this.state.datalist} extradata={this.state}
      refreshing={this.state.isrefreshing}
      onrefresh={() => this._onrefresh()}
      keyextractor={(item, index) => item.id}
      itemseparatorcomponent={() => <view style={{
        height: 1,
        backgroundcolor: '#d6d6d6'
      }}/>}
      renderitem={this._renderitem}
      onlayout={e => this.fheight = e.nativeevent.layout.height}
      listemptycomponent={this.emptycomponent}/>
      
      
  //定义空布局
    emptycomponent = () => {
    return <view style={{
      height: this.fheight,
      alignitems: 'center',
      justifycontent: 'center',
    }}>
      <text style={{
        fontsize: 16
      }}>暂无数据</text>
    </view>
  }

通过上面的调整发现在android上运行时达到我们想要的效果了,但是在ios上,不可控,偶尔居中显示,偶尔又显示到最上面。原因就是在ios上onlayout调用的时机与android略微差别(ios会出现emptycomponent渲染时onlayout还没有回调,此时fheight还没有值)。

所以为了将变化后的值作用到emptycomponent,我们将fheight设置到state中

state={
  fheight:0
}

onlayout={e => this.setstate({fheight: e.nativeevent.layout.height})}

这样设置后应该完美了吧,可是....在android上依然能完美实现我们要的效果,在ios上出现了来回闪屏的的问题。打印log发现值一直是0和测量后的值来回转换。在此处我们仅仅需要是测量的值,所以我们修改onlayout

           onlayout={e => {
             let height = e.nativeevent.layout.height;
             if (this.state.fheight < height) {
               this.setstate({fheight: height})
             }
           }}

经过处理后,在ios上终于完美的实现我们要的效果了。

除了上面的坑之外,个人感觉还有一个坑就是onendreached,如果我们实现下拉加载功能,都会用到这个属性,提到它我们当然就要提到onendreachedthreshold,在flatlist中onendreachedthreshold是一个number类型,是一个他表示具体底部还有多远时触发onendreached,需要注意的是flatlist和listview中的onendreachedthreshold表示的含义是不同的,在listview中onendreachedthreshold表示具体底部还有多少像素时触发onendreached,默认值是1000。而flatlist中表示的是一个倍数(也称比值,不是像素),默认值是2。

那么按照常规我们看下面实现

      <flatlist
        data={this.state.datalist}
        extradata={this.state}
        refreshing={this.state.isrefreshing}
        onrefresh={() => this._onrefresh()}
        itemseparatorcomponent={() => <view style={{
          height: 1,
          backgroundcolor: '#d6d6d6'
        }}/>}
        renderitem={this._renderitem}
        listemptycomponent={this.emptycomponent}
        onendreached={() => this._onendreached()}
        onendreachedthreshold={0.1}/>

然后我们在componentdidmount中加入下面代码

  componentdidmount() {
    this._onrefresh()
  }

也就是进入开始加载第一页数据,下拉的执行onendreached加载更多数据,并更新数据源datalist。看起来是完美的,不过.....运行后你会发现onendreached一直循环调用(或多次执行),有可能直到所有数据加载完成,原因可能大家也能猜到了,因为_onrefresh加载数据需要时间,在数据请求到之前render方法执行,由于此时没有数据,onendreached方法执行一次,那么此时相当于加载了两次数据。

至于onendreached执行多少次就需要onendreachedthreshold的值来定了,所以我们一定要慎重设置onendreachedthreshold,如果你要是理解成了设置像素,设置成了一个比较大的数,比如100,那完蛋了....个人感觉设置0.1是比较好的值。

通过上面的分析,个人感觉有必要对flatlist进行一次二次封装了,根据自己的需求我进行了一次二次封装

import react, {
  component,
} from 'react'
import {
  flatlist,
  view,
  stylesheet,
  activityindicator,
  text
} from 'react-native'
import proptypes from 'prop-types';

export const flatliststate = {
  idle: 0,
  loadmore: 1,
  refreshing: 2
};
export default class com extends component {
  static proptypes = {
    refreshing: proptypes.oneoftype([proptypes.bool, proptypes.number]),
  };
  state = {
    listheight: 0,
  }

  render() {
    var {listemptycomponent,itemseparatorcomponent} = this.props;
    var refreshing = false;
    var emptycontent = null;
    var separatorcomponent = null
    if (listemptycomponent) {
      emptycontent = react.isvalidelement(listemptycomponent) ? listemptycomponent : <listemptycomponent/>
    } else {
      emptycontent = <text style={styles.emptytext}>暂无数据下拉刷新</text>;
    }
    if (itemseparatorcomponent) {
      separatorcomponent = react.isvalidelement(itemseparatorcomponent) ? itemseparatorcomponent :
        <itemseparatorcomponent/>
    } else {
      separatorcomponent = <view style={{height: 1, backgroundcolor: '#d6d6d6'}}/>;
    }
    if (typeof this.props.refreshing === "number") {
      if (this.props.refreshing === flatliststate.refreshing) {
        refreshing = true
      }
    } else if (typeof this.props.refreshing === "boolean") {
      refreshing = this.props.refreshing
    } else if (typeof this.props.refreshing !== "undefined") {
      refreshing = false
    }
    return <flatlist
      {...this.props}
      onlayout={(e) => {
        let height = e.nativeevent.layout.height;
        if (this.state.listheight < height) {
          this.setstate({listheight: height})
        }
      }
      }
      listfootercomponent={this.renderfooter}
      onrefresh={this.onrefresh}
      onendreached={this.onendreached}
      refreshing={refreshing}
      onendreachedthreshold={this.props.onendreachedthreshold || 0.1}
      itemseparatorcomponent={()=>separatorcomponent}
      keyextractor={(item, index) => index}
      listemptycomponent={() => <view
        style={{
          height: this.state.listheight,
          width: '100%',
          alignitems: 'center',
          justifycontent: 'center'
        }}>{emptycontent}</view>}
    />
  }

  onrefresh = () => {
    console.log("flatlist:onrefresh");
    if ((typeof this.props.refreshing === "boolean" && !this.props.refreshing) ||
      typeof this.props.refreshing === "number" && this.props.refreshing !== flatliststate.loadmore &&
      this.props.refreshing !== flatliststate.refreshing
    ) {
      this.props.onrefresh && this.props.onrefresh()
    }

  };
  onendreached = () => {
    console.log("flatlist:onendreached");
    if (typeof this.props.refreshing === "boolean" || this.props.data.length == 0) {
      return
    }
    if (!this.props.pagesize) {
      console.warn("pagesize must be set");
      return
    }
    if (this.props.data.length % this.props.pagesize !== 0) {
      return
    }
    if (this.props.refreshing === flatliststate.idle) {
      this.props.onendreached && this.props.onendreached()
    }
  };


  renderfooter = () => {
    let footer = null;
    if (typeof this.props.refreshing !== "boolean" && this.props.refreshing === flatliststate.loadmore) {
      footer = (
        <view style={styles.footerstyle}>
          <activityindicator size="small" color="#888888"/>
          <text style={styles.footertext}>数据加载中…</text>
        </view>
      )
    }
    return footer;
  }
}
const styles = stylesheet.create({
  footerstyle: {
    flex: 1,
    flexdirection: 'row',
    justifycontent: 'center',
    alignitems: 'center',
    padding: 10,
    height: 44,
  },
  footertext: {
    fontsize: 14,
    color: '#555555',
    marginleft: 7
  },
  emptytext: {
    fontsize: 17,
    color: '#666666'
  }
})

proptypes中我们使用了oneoftype对refreshing类型进行限定,如果listemptycomponent有定义,就是使用自定义分view,同理itemseparatorcomponent也可以自定义。

在下拉加载数据时定义了一个listfootercomponent,用于提示用户正在加载数据,refreshing属性如果是boolean的话,表示没有下拉加载功能,如果是number类型,pagesize必须传,数据源长度与pagesize取余是否等于0,判断是否有更多数据(最后一次请求的数据等于pagesize时才有更多数据,小于就不用回调onendreached)。当然上面的代码也很简单,相信很容易看懂,其它就不多介绍了。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网