列表顯示數(shù)據(jù),基本什么應(yīng)用都是必須。筆者寫作的時候RN版本是0.34。今天就來從淺到深的看看React Native的ListView怎么使用。
首先是使用寫死的數(shù)據(jù),之后會使用網(wǎng)絡(luò)請求的數(shù)據(jù)在界面中顯示。最后加上一個ActivityIndicator,網(wǎng)絡(luò)請求的過程中顯示Loading圖標,加載完成之后顯示數(shù)據(jù),隱藏Loading圖標。
最簡單的
//@flow
import React from 'react';
import {
Text,
View,
ListView
} from 'react-native';
export default class DemoList extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(['row 1', 'row 2'])
};
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>} />
);
}
}
引入所需要的內(nèi)置組件之類的就不多說了。
第一步,在constructor
里設(shè)置數(shù)據(jù)源,并同時指定什么時候重新繪制一行,就是在這個時候(r1, r2) => r1 !== r2}
重繪。
之后,在state里面設(shè)置數(shù)據(jù)源。下面從網(wǎng)絡(luò)請求數(shù)據(jù)的時候state的作用就更加明顯了。RN的組件在state發(fā)生改變的時候就會重繪。這個下面會詳細解釋。
最后,在render
方法里返回ListView,這里的props里有一個renderRow
。在這里指定的代碼就是把數(shù)據(jù)源中每一行的數(shù)據(jù)繪制在Text
里。
一步一步接近實際產(chǎn)品開發(fā)
下面就把繪制行的部分抽象出來。在Native應(yīng)用的開發(fā)中,無論是iOS還是Android,行繪制的部分都是單獨出來的。在RN里雖然可以不獨立出來,但是你也看到了,這樣的寫法遇到稍微復(fù)雜一點的行內(nèi)容的時候就捉襟見肘了。不獨立出來行繪制部分代碼會很難維護。
這部分不復(fù)雜,獨立出來以后是這樣的:
import //...略...
export default class DemoList extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(['row 1', 'row 2'])
};
//bind
this._renderRow = this._renderRow.bind(this);
}
_renderRow(rowData) {
return (
<View style={{height: 50}}>
<Text>{rowData}</Text>
</View>
);
}
render() {
return (
<ListView dataSource={this.state.dataSource}
renderRow={this._renderRow} />
);
}
}
這個例子和上例基本上一樣。只是多了一個_renderRow(rowData)
方法。
注意:在使用這個方法以前,一定要綁定:this._renderRow = this._renderRow.bind(this);
。綁定也可以這樣<ListView dataSource={this.state.dataSource} renderRow={this._renderRow.bind(this)} />
。
在繪制行的時候,比之前稍微有一點改動。行文本的外面套了一個View,并指定這個View的高度為50。
加上裝飾
從現(xiàn)在來看,數(shù)據(jù)只有兩行。如果不滑動一下的話,看起來和兩個上下排列的Text沒有什么區(qū)別。
首先我們加一個分割線:
export default class DemoList extends React.Component {
constructor() {
//記得使用方法之前綁定
this._renderSeparator = this._renderSeparator.bind(this);
}
_renderRow(rowData) {
// ...略...
}
_renderSeparator(sectionID: number, rowID: number, adjacentRowHighlighted: bool) {
return (
<View key={`{sectionID}-${rowID}`}
style={{height: 1, backgroundColor: 'black'}}>
</View>
);
}
render() {
return (
<ListView dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderSeparator={this._renderSeparator}
/>
);
}
}
這里需要額外說明一些,在方法里_renderSeparator(sectionID: number, rowID: number, adjacentRowHighlighted: bool)
我看看到了在參數(shù)的名稱后面都有類型的說明。這個不是ES6的也不是js里的,而是FB自己搞的一套靜態(tài)類型檢查工具里的定義。這個工具叫Flow。
如果你從一開始就沒打算跟flow扯上任何關(guān)系,那么就按照ES標準寫就好。
至于分割線也是非常簡單。我們這就返回了一個高度一個像素的,背景色為黑色的view。
點擊和高亮
Row的點擊不想Native那樣,默認的一般就有了。在RN里,我們需要手動賦予一行可以被點擊的功能。
_renderRow(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
return (
<TouchableHighlight onPress={() => {
this._pressRow(rowID);
highlightRow(sectionID, rowID);
}}>
<View style={styles.row}>
<Text style={styles.text}>{rowData}</Text>
</View>
</TouchableHighlight>
);
}
在RN里處理一般點擊的不二選擇就是TouchableHighlight
。在TouchableHighlight
里的onPress
里調(diào)用自定義的_pressRow方法處理點擊,highlightRow
方法高亮行。
當(dāng)然,這里就少不了用到樣式了:
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'center',
padding: 10,
backgroundColor: '#F6F6F6',
},
text: {
flex: 1,
},
seperator: {
height: 1,
backgroundColor: '#CCCCCC'
}
});
把Cell分離
在實際的開發(fā)中,一般沒有人會把Row(或者行)的繪制和ListView
放在一起。我們這里就演示如何把Row的繪制分離出去。
首先創(chuàng)建一個單獨的文件,定義Cell:
import React from 'react';
import {
View,
Text,
TouchableHighlight,
StyleSheet
} from 'react-native';
export default class DemoCell extends React.Component {
render() {
return (
<View>
<TouchableHighlight onPress={this.props.onSelect}>
<View style={styles.row}>
<Text style={styles.text}>{this.props.rowData}</Text>
</View>
</TouchableHighlight>
</View>
);
}
};
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'center',
padding: 10,
backgroundColor: '#F6F6F6',
},
text: {
flex: 1,
},
});
Row也是一個組件,是一個組件就可以在另外的組建里渲染。所以,單獨定義的Row就是這么用的。
回到demoList.js文件。在_renderRow
方法中修改代碼:
_renderRow(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
return (
// <TouchableHighlight onPress={() => {
// this._pressRow(rowID);
// highlightRow(sectionID, rowID);
// }}>
// <View style={styles.row}>
// <Text style={styles.text}>{rowData}</Text>
// </View>
// </TouchableHighlight>
<DemoCell onSelect={() => {
this._pressRow(rowID);
highlightRow(sectionID, rowID);
}} rowData={rowData}/>
);
}
結(jié)合網(wǎng)絡(luò)請求
ListView在實戰(zhàn)中,除非是Settings之類的界面,數(shù)據(jù)都是從網(wǎng)絡(luò)請求得到的。上一節(jié)中正好已經(jīng)講述了如何使用RN內(nèi)置的fetch請求網(wǎng)絡(luò)數(shù)據(jù)。這一節(jié)中就是用fetch來請求dribbble的數(shù)據(jù)。
在使用dribbble的數(shù)據(jù)之前你需要注冊,獲得Access Token。這是請求認證所必須的。
export default class DemoList extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
isLoading: false,
isLoadingTail: false,
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
filter: this.props.filter,
queryNumber: 0,
};
//...略...
}
//...略...
_getShots(query: string) {
this.setState({
isLoading: true,
queryNumber: this.state.queryNumber + 1,
isLoadingTail: false,
});
api.getShotsByType(query, 1).then((responseData) => {
this.setState({
isLoading: false,
dataSource: this._getDataSource(responseData),
});
}).catch((error) => {
this.LOADING[query] = false;
this.resultsCache.dataForQuery[query] = undefined;
this.setState({
dataSource: this._getDataSource([]),
isLoading: false,
});
});
}
還是在類DemoList
里,其他無關(guān)緊要的代碼先略去。要緊的地方是需要注意在constructor
里設(shè)置state的時候dataSource
如何設(shè)置的。
state的改變會影響到組件的繪制。所以,在_getShots
方法里,開始請求之前先設(shè)置一個默認的state狀態(tài)。在請求成功之后使用setState
設(shè)置一個,在catch到異常的時候再顯示另外一個。
在state
里還有一個屬性叫做isLoading: false,
。這個是影控制Loading視圖的。在false的時候隱藏,在true的時候顯示。
那么loading界面是什么樣呢?
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1, backgroundColor: 'white'}}>
<ActivityIndicator animating={true}
style={[styles.centering]}
size="large"
color="#cccccc"
/>
</View>
組合起來
在類DemoList
里組合相關(guān)代碼:
_renderView() {
if (this.state.isLoading) {
return (
<UNActivityIndicator loadingType={LOADING_TYPE.Large} />
);
}
return (
<View style={styles.container}>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderSeparator={this._renderSeparator}
automaticallyAdjustContentInsets={false}
/>
</View>
);
}
在renderView的時候,先檢查state.isLoading
,如果需要loading視圖,那么返回loading視圖,其他的不返回。數(shù)據(jù)加載成功之后state.isLoading
被設(shè)置為false,那么顯示ListView。
填坑完畢
以上就是處理ListView和其中的Cell的一些常見問題的方法。