React Native开发React Native开发经验集程序员

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

2019-08-13  本文已影响346人  学生黄哲

Demo地址

先上图

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

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

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.isRequired,//分組組頭的高度
        Index_Height: PropTypes.number.isRequired,  //分組每一項的高度
    }
    componentWillUnmount() {
        this.timer && clearTimeout(this.timer)
    }
    static defaultProps = {
        showHeader: false                           //默认不展示头部
    };
    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, renderSection, renderHeader } = this.props
        const top_offset = indexArray ? (screenH - indexArray.length * 15) / 3 : 0
        return (
            <View style={styles.Box}>
                <LargeList
                    renderIndexPath={renderItem}
                    renderSection={renderSection}
                    refreshHeader={UpPullLoading}
                    renderFooter={this._renderFooter}
                    onRefresh={this.props.UpPullRefresh}
                    ref={ref => (this._LargeList = ref)}
                    heightForSection={() => Section_Height}
                    heightForIndexPath={() => Index_Height}
                    data={dataArray ? dataArray : { items: [] }}
                    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 >
        )
    }
}
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,
  }
});
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地址

上一篇下一篇

猜你喜欢

热点阅读