React-Native实现高性能城市列表/联系人列表。

Demo地址

先上图

项目需要用到分组列表和字母定位相应功能,尝试下使用RN官方组件SectionList效果可以实现,但发现数据量大的时候SectionList渲染数据效率很低,当滑动过快的时候很容易出现白屏状态。加载也相对较慢。
于是使用react-native-largelist自己稍作封装,效率确实很强大。

首先感谢一下react-native-largelist的作者 GitHub地址
demo有展示下拉加载的Loading,可以使用RN官方组件ActivityIndicator来作为loading效果。
demo使用的是react-native-spinkit简单美观。

  • 主UI class
import React, { Component } from 'react'
import {
    View, Text, Dimensions, FlatList,
    TouchableOpacity, ViewPropTypes,
} from 'react-native'
import styles from './Styles'
import PropTypes from 'prop-types'
let screenH = Dimensions.get('window').height;
import UpPullLoading from './UpPullLoading'
import { LargeList } from "react-native-largelist-v3";
export default class List extends Component {
    static propTypes = {
        UpPullRefresh: PropTypes.func,              //是否展示下拉刷新,下拉刷新的回调
        showHeader: PropTypes.bool,                 //是否展示头部组件
        renderHeader: PropTypes.func,               //头部组件
        renderSection: PropTypes.func,              //分組頭组建
        renderItem: PropTypes.func,                 //分組每一項组件
        ItemBoxStyle: ViewPropTypes.style,          //导航容器样式
        showHeaderBoxStyle: ViewPropTypes.style,    //导航第一个容器额外样式
        showHeaderStyle: PropTypes.object,          //导航第一个Text的额外样式
        flatBoxStyle: ViewPropTypes.style,          //導航List的样式
        letterStyle: PropTypes.object,              //導航每一個Text的样式
        indexArray: PropTypes.array,                //导航數組,有则展示右侧导航
        dataArray: PropTypes.array.isRequired,      //数据源数组
        HeaderHeight: PropTypes.number,             //头部高度
        Section_Height: PropTypes.number,           //分組組頭的高度
        Index_Height: PropTypes.number.isRequired,  //分組每一項的高度
    }
    componentWillUnmount() {
        this.timer && clearTimeout(this.timer)
    }
    static defaultProps = {
        Index_Height: 50,
        Section_Height: 0,
        showHeader: false,                           //默认不展示头部
        UpPullRefresh: () => null,
        renderSection: () => null,
    };
    constructor(props) {
        super(props)
    }
    //计算偏移量
    getOfset = (key) => {
        const { dataArray, Index_Height, Section_Height, HeaderHeight, showHeader } = this.props
        let [hKey, itemkey, sectionKey, hot_height] = [key, 0, 0, 0]
        //如果展示头部则加上头部高度
        if (showHeader) {
            if (key > 0) hKey = key - 1
            hot_height = key ? HeaderHeight : 0
        }
        for (i = 0; i < hKey; i++) {
            for (index = 0, len = dataArray[i].items.length; index < len; index++) {
                itemkey++
            }
            sectionKey++
        }
        return (itemkey * Index_Height + sectionKey * Section_Height) + hot_height
    }
    _onSectionselect = (value, key) => {
        const ofset = this.getOfset(key)
        if (this._LargeList) {
            this._LargeList.scrollTo({
                x: 0, y: ofset
            });
        }
    };
    _renderFooter = () => {
        return (
            <View style={{ height: 10 }} />
        )
    }
    _FlatItem = ({ item, index }) => {
        const { ItemBoxStyle, letterStyle, showHeaderBoxStyle, showHeaderStyle } = this.props
        const hot = index == 0
        return (
            <TouchableOpacity style={[styles.TextBox, ItemBoxStyle, hot && showHeaderBoxStyle]}
                onPressIn={({ nativeEvent: e }) => this._onSectionselect(e, index)}>
                <Text style={[styles.indexText, letterStyle, hot && showHeaderStyle,]}>
                    {item}
                </Text>
            </TouchableOpacity>
        )
    }
    endUpPullRefresh = _ => {
        this.timer = setTimeout(() => {
            if (this._LargeList)
                this._LargeList.endRefresh();
        }, 1000);
    }
    render() {
        const { indexArray, dataArray, Section_Height, Index_Height, showHeader, renderItem,
            UpPullRefresh, renderSection, renderHeader } = this.props
        const top_offset = indexArray ? (screenH - indexArray.length * 15) / 3 : 0
        return (
            <View style={styles.Box}>
                <LargeList
                    onRefresh={UpPullRefresh}
                    renderSection={renderSection}
                    renderIndexPath={renderItem}
                    refreshHeader={UpPullLoading}
                    renderFooter={this._renderFooter}
                    data={dataArray ? dataArray : []}
                    ref={ref => (this._LargeList = ref)}
                    heightForSection={() => Section_Height}
                    heightForIndexPath={() => Index_Height}
                    renderHeader={showHeader ? renderHeader : () => null}
                />
                {
                    indexArray &&
                    <View style={[styles.flatBox, {
                        top: top_offset
                    }, this.props.flatBoxStyle]}>
                        <FlatList
                            data={indexArray}
                            renderItem={this._FlatItem}
                            keyExtractor={(item, index) => index.toString()}       //不重复的key
                            initialNumToRender={indexArray ? indexArray.length : 10}
                        />
                    </View>
                }
            </View >
        )
    }
}
  • Loading class
    这里的加载loading使用的是react-native-spinkit如果不想装库可以使用ActivityIndicator
import React from "react";
import {
  Animated, View, StyleSheet, Text
} from "react-native";
import arrow from './arrow.png'
import { Colors } from "../../../Themes";
import Spinner from "react-native-spinkit";
import { RefreshHeader } from "react-native-spring-scrollview/RefreshHeader";
export default class UpPullLoading extends RefreshHeader {
  static height = 80;

  static style = "stickyContent";

  render() {
    return (
      <View style={styles.container}>
        {this._renderIcon()}
        {this._renderText()}
      </View>
    );
  }
  _renderText = _ => {
    const s = this.state.status;
    if (s === 'refreshing') {
      return (
        <View />
      )
    } else {
      return (
        <View style={styles.rContainer}>
          <Text style={styles.text}>
            {this.getTitle()}
          </Text>
        </View>
      )
    }
  }
  _renderIcon = _ => {
    const s = this.state.status;
    if (s === "refreshing") {
      return <Spinner size={36} type="9CubeGrid" color={Colors.Subject} style={{ alignSelf: 'center' }} />
    }
    const { maxHeight, offset } = this.props;
    return (
      <Animated.Image
        source={arrow}
        style={{
          tintColor: Colors.Subject,
          transform: [
            {
              rotate: offset.interpolate({
                inputRange: [-maxHeight - 1 - 10, -maxHeight - 10, -50, -49],
                outputRange: ["180deg", "180deg", "0deg", "0deg"]
              })
            }
          ]
        }}
      />
    );
  }

  getTitle() {
    const s = this.state.status;
    switch (s) {
      case "pulling":
        return "下拉刷新"
      case "waiting":
        return "下拉刷新"
      case "pullingEnough":
        return "松开刷新"
      case "refreshing":
        return "请稍等..."
      case "pullingCancel":
        return "放弃刷新"
      case "rebound":
        return "刷新完成"
      default:
        break;
    }
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    flexDirection: "row"
  },
  rContainer: {
    marginLeft: 10
  },
  text: {
    marginVertical: 5,
    fontSize: 15,
    color: Colors.Subject,
  }
});
  • 使用的时候只需要导入List class使用方法如下
    dataArray 为主数据数组如要使用分组列表 。数组结构应为[{xx:xx,items:[]}]。
    当然可以只用作不需要分组的List使用数结构为只需要dataArray=[{items:你的数组}]
import React, { Component, Fragment } from 'react'
import {
    View, Text, Image,
    FlatList, TouchableOpacity, 
} from 'react-native'
import { Colors, Styles, Px } from '../../../Themes'
import List from '../../../Component/List'
import styles from './Styles'
import cityIndex from './Config/cityIndex'
import hotCities from './Config/hotCities'
import alphabeticalIndex from './Config/alphabeticalIndex'
import location from '../../../Images/Home/location.png'
import close from '../../../Images/Component/guanbi.png'
import Header from '../../../Component/Header';
import PropTypes from 'prop-types'
const Section_Height = Px(87)
const Index_Height = Px(80)
const HotHeight = Px(402)
export default class CityList extends Component {
    static propTypes = {
        ChoosingCity: PropTypes.func,
        closeModal: PropTypes.func,
    }
    constructor(props) {
        super(props)
    }
    _renderSection = (index) => {
        const contact = cityIndex[index];
        return (
            <View style={styles.SectionBox}>
                <Text style={styles.SectionText}>{contact.sortLetters}</Text>
            </View>
        )
    }
    _renderItem = ({ section: section, row: row }) => {
        const item = cityIndex[section].items[row];
        return (
            <TouchableOpacity style={styles.ItemBox}
                onPress={() => this.props.ChoosingCity(item.name)}
            >
                <Text style={styles.ItemTetx}>{item.name}</Text>
                <View style={styles.border} />
            </TouchableOpacity>
        )
    }
    _flatItem = ({ item, index }) => {
        return (
            <TouchableOpacity style={styles.flatItemBox}
                onPress={() => this.props.ChoosingCity(item.name)}
            >
                <Text style={styles.ItemTetx} >{item.name}</Text>
            </TouchableOpacity>
        )
    }
    LocatingCity = _ => {
        return (
            <View>
                <View style={styles.SectionBox}>
                    <Text style={styles.SectionText}>当前定位城市</Text>
                </View>
                <View style={[styles.flatItemBox, styles.LocatingBox]}>
                    <Image style={styles.ImageStyles} source={location} />
                    <Text style={[styles.ItemTetx, { color: Colors.white }]} >北京市</Text>
                </View>
            </View>
        )
    }
    _renderHeader = _ => {
        return (
            <View>
                {this.LocatingCity()}
                <View style={styles.SectionBox}>
                    <Text style={styles.SectionText}>{hotCities.sortLetters}</Text>
                </View>
                <View style={styles.FlatBox}>
                    <FlatList
                        numColumns={3}
                        data={hotCities.items}
                        renderItem={this._flatItem}
                        keyExtractor={(item, index) => `item${index}`}
                    />
                </View>
            </View>

        )
    }
    _LeftComponent = _ => {
        return (
            <TouchableOpacity onPress={this.props.closeModal}>
                <Image source={close} style={Styles.closeStyle} />
            </TouchableOpacity>
        )
    }
    _UpPullRefresh = _ => {     
           //结束刷新状态
            this._list.endUpPullRefresh()
    }
    render() {
        return (
            <Fragment>
                <Header
                    title={'选择城市'}
                    showStatusBar={false}
                    headerBgColor={Colors.Subject}
                    titleColor={Colors.white}
                    LeftComponent={this._LeftComponent} >
                </Header>
                <View style={styles.Box}>
                    <List
                        showHeader={true}
                        dataArray={cityIndex}                       
                        HeaderHeight={HotHeight}
                        Index_Height={Index_Height}
                        renderItem={this._renderItem}
                        indexArray={alphabeticalIndex}
                        ref={ref => (this._list = ref)}
                        Section_Height={Section_Height}
                        renderHeader={this._renderHeader}
                        UpPullRefresh={this._UpPullRefresh}          //下拉刷新
                        renderSection={this._renderSection}
                        showHeaderStyle={styles.showHeaderStyle}
                        showHeaderBoxStyle={styles.showHeaderBoxStyle}
                    />
                </View>
            </Fragment>
        )
    }
}

demo只展示了城市列表,手机联系人列表也是一个道理,只需要更改数据源就可以。完整demo地址

Demo地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容