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地址