FlatList元件學習和封裝
是一個用於呈現簡單的平面列表的高效能元件,支援下面功能:
- 跨平臺
- 支援水平
- 支援可配置的可視性回撥
- 支援頭部
- 支援尾部
- 支援下拉重新整理
- 支援上來載入
- 支援跳轉到指定行 他是繼承自ScrollView以及VirtualizedList的,所以具有他們二者的props;
簡單例子
render(){
return <View>
<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
</View>
}
複製程式碼
其中包含了最重要兩個元件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 <View style={{
height: 1,
backgroundColor: "#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 = (<View style={footerContainerStyle}/>);
break;
case State.ERROR: {
//是否有資料
footer = hasData ? (
<TouchableOpacity
activeOpacity={0.8}
style={footerContainerStyle}
onPress={this._reRequest}
>
<Text style={footerTextStyle}>{loadErrorText}</Text>
</TouchableOpacity>
) : (<EmptyData onPress={this._reRequest} tips={loadErrorText}/>);
break;
}
case State.NO_DATA: {
footer = <EmptyData onPress={this._reRequest} tips={loadEmptyText}/>;
break;
}
case State.LOADING: {
footer = (
<View style={footerContainerStyle}>
<ActivityIndicator size="small" color="#888888"/>
<Text style={[footerTextStyle, { marginLeft: 7 }]}>{loadingText}</Text>
</View>
);
break;
}
case State.LOAD_END: {
footer = (
<View style={footerContainerStyle}>
<Text style={footerTextStyle}>{loadEndText}</Text>
</View>
);
break;
}
}
return footer;
};
複製程式碼
其中Empty是空資料的元件,另外獨立定義了他的Component
class EmptyData extends Component {
static defaultProps = {
tips: "暫無資料",
};
_onPress = () => {
const { onPress } = this.props;
if (onPress) {
onPress(true);
}
};
render() {
return (
<TouchableOpacity onPress={this._onPress} activeOpacity={0.8}>
<View style={{
flex: 1,
alignItems: "center",
justifyContent: "center"
}}>
<Image
style={{
marginTop: 10,
marginBottom: 10,
width: 120,
height: 120,
resizeMode: "contain"
}}
source={require("../../img/pic_empty_data.png")}
/>
<Text>{this.props.tips}</Text>
</View>
</TouchableOpacity>
);
}
}
複製程式碼
- render方法
/**
* 渲染
* @returns {*}
*/
render() {
let {
renderItem = () => {
}
} = this.props;
return (
<FlatList
onEndReached={this._onEndReached}
onRefresh={this.state.enableRefresh ? this._onRefresh : null}
refreshing={this.state.enableRefresh && (this.props.requestState === State.REFRESHING)}
ListFooterComponent={this.state.enableLoad ? this._renderFooter : null}
onEndReachedThreshold={0.1}
renderItem={renderItem}
{...this.props}
ItemSeparatorComponent={this._separator}
keyExtractor={this._keyExtractor} Ò
/>
);
}
}
複製程式碼
這樣子我們就定義了一個完整的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 <Text style={{
color: "black",
textAlign: "center",
}}>{title}</Text>;
};
render() {
console.log("requestState=" + this.state.requestState);
return (
<View>
<PullLoadComponent
ref={(flat_list) => {
this.flat_list = flat_list;
}}
onRequest={this._request}
data={this.state.data}
requestState={this.state.requestState}
renderItem={this._renderItem}
/>
</View>
);
}
}
複製程式碼
Demo地址: ReactNativeDemo