FlatList组件学习和封装
是一个用于呈现简单的平面列表的高性能组件,支持下面功能:
- 跨平台
- 支持水平
- 支持可配置的可视性回调
- 支持头部
- 支持尾部
- 支持下拉刷新
- 支持上来加载
- 支持跳转到指定行 他是继承自ScrollView以及VirtualizedList的,所以具有他们二者的props;
简单例子
render(){
return
'a'}, {key: 'b'}]}
renderItem={({item}) => {item.key} }
/>
}
复制代码
其中包含了最重要两个组件data
和renderItem
,data是数据list,是一个一个对象组合而成的,renderItem是条目。
FlatList重要方法
1 renderItem 他是一个方法,用于渲染具体的Item,他回调有几个参数,定义如下
renderItem(item,index,separators){
}
复制代码
需要注意的是我们使用的时候一般需要进行解构赋值去取item的单个内容,下面谈到封装的时候再给例子。
2 data 他是FlastList的数据源,是一个数组
3 ListEmptyComponent 需要返回一个Component,当FlatList数据为空的时候的组件
4 ListFooterComponent 需要返回一个Component,FlatList的footer组件
5 ListHeaderComponent 需要返回一个Component,FlatList的头部组件,需要注意他不是刷新的那个组件,只是一个不同于普通item的头部组件
6 horizontal 一个布尔值,表明 是垂直的还是水平的FlatList,true是水平,false是垂直。
7 keyExtractor 定义是
(item: object, index: number) => string;
复制代码
指定一个function,返回item的index,相当于Android的adapter的getIndex()方法,返回一个独立Item的唯一标识,需要返回一个index
8 numColumns 是一个number,表明该FlatList是几列。
9 onEndReached 当FlatLst到达底部的时候的回调
10 onRefresh FlatList触发刷新时候的回调
11 refreshing 控制是否处于刷新状态
12 ItemSeparatorComponent 需要返回一个Component,作为item之间的分割线
13 FlatList提供的一些scroll方法,比如滚动到底部,滚动到具体item,滚动多少距离等。
scrollToEnd //滚动到底部
scrollToIndex //滚动到第几个item
scrollToItem //滚动到某个item
scrollToOffset //滚动多少距离
recordInteraction //告诉列表滚动发送了,会去调用viewability的计算。
flashScrollIndicators //随时显示滚动指示器
复制代码
封装FlatList
对于FlatList的刷新样式,我们能够定义或者是改造的可行性比较难,他的样式一般是比较固定,我们,假如需要自定义刷新的组件,可以参考我给出的推荐学习。
对于FlatList的封装,整体需要做到开闭原则,我们有几点需求需要明确:
- 提供刷新+加载
- 可分别设置是否刷新+加载是否可用
- 提供样式外部可定义以及一套默认样式
- 提供多种列表状态,比如刷新中,加载中,加载完成,加载失败,空数据等
- 提供一个onRequest(isRefresh:boolean)方法给外部,进行刷新或者加载处理
- 对于一些方法,提供默认配置,但是外部也可以进行修改。
- 定义FlatList的状态,默认props,styles以及需要的state
//state
export const State = {
NORMAL: 0,//正常状态
REFRESHING: 1,//刷新中
LOADING: 2,//正在加载
LOAD_END: 3,//上拉加载完成
ERROR: 4,//上拉加载发生错误
NO_DATA: 5,//无数据情况
};
/**
* 默认样式
*/
const styles = StyleSheet.create({
//底部默认样式
footerContainer: {
flex: 1,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 10,
height: 44,
},
footerText: {
fontSize: 14,
color: "#555555"
}
});
//props+state
static defaultProps = {
loadingText: "数据加载中...",
loadErrorText: "点击重新加载...",
loadEndText: "已加载全部数据",
loadEmptyText: "暂时没有相关数据",
requestState: State.NORMAL,
footerContainer: {},
footerText: {},
data: [],
id: "flat_list",
onRequest: (isRefresh) => {
},
};
constructor(props) {
super(props);
//是否刷新/加载可用,外部可以通过调用方法设置,默认都可以
this.state = {
enableLoadMore: true,
enableRefresh: true,
};
}
复制代码
- 外部设置是否可用方法
/**
* 不会显示顶部可以刷新的UI
* @param enableLoad
*/
setEnableLoad(enableLoad) {
this.setState({
enableLoad: enableLoad,
});
}
/**
* 他是不会显示底部刷新的UI的
* @param enableRefresh
*/
setEnableRefresh(enableRefresh) {
this.setState({
enableRefresh: enableRefresh,
});
}
复制代码
- 数据加载方法
/**
* 刷新触发
* @private
*/
_onRefresh = () => {
if (this._enableRefresh()) {
this.props.onRequest && this.props.onRequest(true);
}
};
/**
* 是否可以顶部刷新
* 当前需要 非刷新,而且也不是正在加载
* @returns {boolean}
*/
_enableRefresh = () => {
return !(this.props.requestState === State.REFRESHING || this.props.requestState === State.LOADING);
};
_onEndReached = () => {
if (this._enableLoad()) {
this.props.onRequest && this.props.onRequest(false);
}
};
/**
* 是否可以加载,当前数据不能是0(因为进入的时候必须是refresh获取的数据显示)
* @returns {boolean}
*/
_enableLoad = () => {
let { requestState, data } = this.props;
if (data.length === 0) {
return false;
}
return requestState === State.NORMAL;
};
/**
* 刷新或加载
* @param isRefresh
* @private
*/
_reRequest = (isRefresh) => {
//回调外部方法
this.props.onRequest && this.props.onRequest(isRefresh);
};
_keyExtractor = (item, index) => {
const { keyExtractor } = this.props;
if (keyExtractor) {
return keyExtractor(item, index);
}
return index.toString();
};
复制代码
- Item的唯一标识以及默认的分割线
_keyExtractor = (item, index) => {
const { keyExtractor } = this.props;
if (keyExtractor) {
return keyExtractor(item, index);
}
return index.toString();
};
_separator = () => {
const { separator } = this.props;
if (separator) {
return separator();
}
return "#ededed",
marginLeft: 10,
marginRight: 10
}}/>;
};
复制代码
- Footer,footer对于不同的FlatList状态会返回不同的Component
/**
* 渲染底部
* @returns {*}
*/
_renderFooter = () => {
let footer = null;
let footerContainerStyle = [styles.footerContainer, this.props.footerContainer];
let footerTextStyle = [styles.footerText, this.props.footerText];
let { loadingText, loadErrorText, loadEndText, loadEmptyText } = this.props;
const hasData = this.props.data && this.props.data.length > 0;
switch (this.props.requestState) {
case State.NORMAL:
footer = ();
break;
case State.ERROR: {
//是否有数据
footer = hasData ? (
{loadErrorText}
) : ();
break;
}
case State.NO_DATA: {
footer = ;
break;
}
case State.LOADING: {
footer = (
"small" color="#888888"/>
{loadingText}
);
break;
}
case State.LOAD_END: {
footer = (
{loadEndText}
);
break;
}
}
return footer;
};
复制代码
其中Empty是空数据的组件,另外独立定义了他的Component
class EmptyData extends Component {
static defaultProps = {
tips: "暂无数据",
};
_onPress = () => {
const { onPress } = this.props;
if (onPress) {
onPress(true);
}
};
render() {
return (
"center",
justifyContent: "center"
}}>
"contain"
}}
source={require("../../img/pic_empty_data.png")}
/>
{this.props.tips}
);
}
}
复制代码
- render方法
/**
* 渲染
* @returns {*}
*/
render() {
let {
renderItem = () => {
}
} = this.props;
return (
);
}
}
复制代码
这样子我们就定义了一个完整的PullLoadComponent。
使用
我们用玩Android开发的Api,这里感谢玩Android提供的Api接口。
const API = "http://www.wanandroid.com/article/list/";
export default class FlatListDemoPage extends Component {
static navigationOptions = {
// headerTitle: 'first',
title: "FlatListDemoPage",
};
constructor(props) {
super(props);
this.state = {
requestState: State.NORMAL,
data: []
};
this.currentPage = 0;
}
componentDidMount() {
this._request(true);
}
_request = (isRefresh) => {
this.currentPage = isRefresh ? 0 : this.currentPage + 1;
this.setState({ requestState: isRefresh ? State.REFRESHING : State.LOADING });
//发起请求
fetch(API + this.currentPage + "/json") // 返回一个Promise对象
.then((res) => {
return res.json();
})
.then((res) => {
const { data } = res;
const { datas } = data;
this._setData(datas);
})
.catch(e => {
//加载失败
console.log(e);
this.setState({ requestState: State.ERROR });
});
};
_setData = (data, isRefresh) => {
if (data && data.length > 0) {
//判断一下是否还有下一页,因为一页最多20条,不满足则是无法去加载更多了
this.setState({
requestState: data.length >= 20 ? State.NORMAL : State.LOAD_END,
data: isRefresh ? data : this.state.data.concat(data)
});
} else {
//已经没有数据了,需要对footer进行处理
if (this.currentPage === 0) {
//第一页没有数据,那么就是当前接口无数据
this.setState({
requestState: State.NO_DATA,
data: []
});
} else {
//不是第一页,新页返回空,就是接下来没有数据了
this.setState({
requestState: State.LOAD_END,
data: this.state.data
});
}
}
};
_renderItem = ({ item }) => {
const { title } = item;
return "black",
textAlign: "center",
}}>{title} ;
};
render() {
console.log("requestState=" + this.state.requestState);
return (
{
this.flat_list = flat_list;
}}
onRequest={this._request}
data={this.state.data}
requestState={this.state.requestState}
renderItem={this._renderItem}
/>
);
}
}
复制代码
Demo地址: ReactNativeDemo
推荐学习
- 官网
- React Native 之自定义下拉刷新