目的
今天我要實現(xiàn)一個 類似于 iOS 開發(fā)中帶有分組的colllectionView 樣式的布局, 每個section都要有個組頭。
首先我們要先決定要使用什么控件。ScrollView
和ListView
/FlatList
還有SectionList
都是可以選擇的。
-
ScrollView
會把所有子元素一次性全部渲染出來。使用上最簡單。但是如果你有一個特別長的列表需要顯示,可能會需要好幾屏的高度。這時就會占用很大的內(nèi)存去創(chuàng)建和渲染那些屏幕以外的JS組件和原生視圖,性能上也會有所拖累。 -
ListView
更適用于長列表數(shù)據(jù)。它會惰性渲染子元素,并不會立即渲染所有元素,而是優(yōu)先渲染屏幕上可見的元素。 -
FlatList
是0.43版本開始新出的改進版的ListView,性能更優(yōu),但是官方說現(xiàn)在可能不夠穩(wěn)定,尚待時間考驗。但是它不能夠分組/類/區(qū)(section)。 -
SectionList
也是0.43版本推出的, 高性能的分組列表組件。但是它不支持頭部吸頂懸浮的效果,但是也不要傷心,官方在下一個版本開始就可以支持懸浮的section頭部啦 ??。
好啦, 綜上所訴我選擇使用SectionList
,現(xiàn)在開始干活吧 ??
首先
首先第一步我們先把要顯示的樣式寫好作為子控件, 把數(shù)據(jù)源整理好。
例一、
<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // 不同section渲染相同類型的子組件
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>
例二、
<SectionList
sections={[ // 不同section渲染不同類型的子組件
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>
sections 就是我們的數(shù)據(jù)源,每一個data 就是我們要用的item, renderItem就是你要顯示的子控件哦。如果你每個組都復(fù)用一個子組件那就按照例一的結(jié)構(gòu), 如果你想要不同的組返回不同樣式的子組件那就按照例二的結(jié)構(gòu)返回不同的renderItem即可。
***這里提個醒, key一定要有, 不同的section 要設(shè)置不同的key才會渲染相應(yīng)的section, 如果你key值都相同, 那可能會出現(xiàn)只顯示一組數(shù)據(jù)的情況哦~ ***
下面來看看我的代碼:
<SectionList
renderItem={this._renderItem}
renderSectionHeader={this._renderSectionHeader}
sections={[ // 不同section渲染相同類型的子組件
{ data: [{ title: this.state.appModel[0] }], key: this.state.groupsModel[0].title },
{ data: [{ title: this.state.appModel[1] }], key: this.state.groupsModel[1].title },
{ data: [{ title: this.state.appModel[2] }], key: this.state.groupsModel[2].title },
{ data: [{ title: this.state.appModel[3] }], key: this.state.groupsModel[3].title },
]}
/>
這樣有了最基礎(chǔ)的樣式, 四組縱向的列表, 但是我要橫向的, 于是我要設(shè)置他的樣式啦。
接下來
這里我添加兩個屬性:
contentContainerStyle={styles.list}//設(shè)置cell的樣式
pageSize={4} // 配置pageSize確認網(wǎng)格數(shù)量
const styles = StyleSheet.create({
list: {
//justifyContent: 'space-around',
flexDirection: 'row',//設(shè)置橫向布局
flexWrap: 'wrap', //設(shè)置換行顯示
alignItems: 'flex-start',
backgroundColor: '#FFFFFF'
},
});
好啦, 讓我們來看看效果。
??這是什么鬼???
為什么它的組頭也在左邊 , 并且他的其他組數(shù)據(jù)都橫著了, 對于小白我來說只有大寫的懵~。不知道你們有沒有遇到這種情況, 是什么原因?qū)е碌模?我很是困惑啊, 當(dāng)我把
renderSectionHeader={this._renderSectionHeader}
這行代碼注掉的時候, 它的顯示是正常的...
這就尷尬了...
它的每一個小方塊是一個item,達不到我要的效果啊, 于是我決定換個思路, 誰讓我是打不死的小白呢??
重新來
我決定讓每個section是一個item。在每個item上創(chuàng)建多個可點擊的板塊。show time ~ ~
_renderItem = ({ item}) => (
<View style={styles.list}>
{
item.map((item, i) => this.renderExpenseItem(item, i))
}
</View>
);
renderExpenseItem(item, i) {
return <TouchableOpacity key={i} onPress={() => this._pressRow(item)} underlayColor="transparent">
<View style={styles.row}>
<CellView source={item.img}></CellView>
</View>
</TouchableOpacity>;
}
_renderSectionHeader = ({ section }) => (
<View style={{ flex: 1, height: 25 }}>
<Text style={styles.sectionHeader} >{section.key}</Text>
</View>
);
render() {
return (
<View style={{ flex: 1 }}>
<Text style={styles.navigatorStyle}> 發(fā)現(xiàn) </Text>
<View style={{ flex: 1, backgroundColor: '#F7F6F8' }}>
<SectionList
renderItem={this._renderItem}
renderSectionHeader={this._renderSectionHeader}
showsVerticalScrollIndicator={false}
sections={ // 不同section渲染相同類型的子組件
this.state.dataSource
}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
list: {
//justifyContent: 'space-around',
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
backgroundColor: '#FFFFFF'
},
row: {
backgroundColor: '#FFFFFF',
justifyContent: 'center',
width: (ScreenWidth - 1) / 4,
height: (ScreenWidth - 1) / 4,
alignItems: 'center',
},
sectionHeader: {
marginLeft: 10,
padding: 6.5,
fontSize: 12,
color: '#787878'
},
});
這里的dataSource 我是在之前數(shù)據(jù)的基礎(chǔ)上又包了一層[],然后在renderItem里做了map映射, 這樣每個renderItem上返回了每一組我所需要的子組件。快來看看我的變化吧??
腫么樣, 達到效果了吧, 但是你有沒有發(fā)現(xiàn) 底部為啥是黃色的?,我可沒有去設(shè)置這么丑的顏色哦,其實它是提醒我們有不完美的地方, 下面就讓我們解決一下這個不完美吧 。
最后解決問題
最后讓我們來解決問題。它警告我們每個item 要有不同的key ,還記不記得我上面的提醒,我也犯這個錯誤了。
- 默認情況下每行都需要提供一個不重復(fù)的key屬性。你也可以提供一個keyExtractor函數(shù)來生成key。
把這個屬性添加到 <SectionList/> 里面
keyExtractor = {this._extraUniqueKey}
_extraUniqueKey(item ,index){
return "index"+index+item;
}
這是每個item要設(shè)置key, 同樣每個子控件也不能放過, 一定要設(shè)置它的key, 要不然這個屎黃色一直伴著你 多煩~~~
最后看一下我最終的代碼吧!
var Dimensions = require('Dimensions');//獲取屏幕的寬高
var ScreenWidth = Dimensions.get('window').width;
var ScreenHeight = Dimensions.get('window').height;
// const AnimatedSectionList = Animated.createAnimatedComponent(SectionList);// 這個是創(chuàng)建動畫
export default class Explore extends Component {
constructor(props) {
super(props);
this.state = {
appModel: null,
groupsModel: null,
dataSource: null,
}
}
//Component掛載完畢后調(diào)用
componentDidMount() {
this.fetchData();
}
async fetchData() {
try {
let model = await NetFetch.post(_req_url_path, {
});
let apps = model.apps;
let groups = model.groups;
let data = [];
for (let i = 0; i < model.groups.length; i++) {
let row = [];
for (let j = 0; j < model.apps.length; j++) {
if (model.groups[i].appIds.indexOf(model.apps[j].appId) >= 0) {
row.push(model.apps[j]);
}
}
data.push({ data: [row], key: model.groups[i].title });
}
// 這里我重組了一下數(shù)據(jù)結(jié)構(gòu), 看沒看見我在row外面又包了一層, 為了我循環(huán)創(chuàng)建每個section的子組件。
this.setState({
appModel: model.apps,
groupsModel: model.groups,
dataSource: data
});
} catch (error) {
alert(error.msg);
}
}
_renderItem = ({ item}) => (
<View style={styles.list}>
{
item.map((item, i) => this.renderExpenseItem(item, i))
}
</View>
);
renderExpenseItem(item, i) {
return <TouchableOpacity key={i} onPress={() => this._pressRow(item)} underlayColor="transparent">
<View style={styles.row}>
<CellView source={item.img}></CellView>
</View>
</TouchableOpacity>;
}
_renderSectionHeader = ({ section }) => (
<View style={{ flex: 1, height: 25 }}>
<Text style={styles.sectionHeader} >{section.key}</Text>
</View>
);
_listHeaderComponent() {
return (
<HeaderView integral={0}></HeaderView>
);
}
_listFooterComponent() {
return (
<Text style={[styles.remark]}>*預(yù)期收益非平臺承諾收益,市場有風(fēng)險,投資需謹慎</Text>
);
}
_pressRow(item) {
this.props.navigator.pushTo(item.go)
}
_extraUniqueKey(item ,index){
return "index"+index+item;
}
render() {
if (!this.state.dataSource) {
return (
<View></View>
);
}
return (
<View style={{ flex: 1 }}>
<Text style={styles.navigatorStyle}> 發(fā)現(xiàn) </Text>
<View style={{ flex: 1, backgroundColor: '#F7F6F8' }}>
<SectionList
contentInset={{top:0,left:0,bottom:49,right:0}}// 設(shè)置他的滑動范圍
renderItem={this._renderItem}
ListFooterComponent={this._listFooterComponent}
ListHeaderComponent={this._listHeaderComponent}
renderSectionHeader={this._renderSectionHeader}
showsVerticalScrollIndicator={false}
keyExtractor = {this._extraUniqueKey}// 每個item的key
// contentContainerStyle={styles.list}
// horizontal={true}
// pageSize={4} // 配置pageSize確認網(wǎng)格數(shù)量
sections={ // 不同section渲染相同類型的子組件
this.state.dataSource
}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
navigatorStyle: {
height: 64,
backgroundColor: '#FFFFFF',
textAlign: 'center',
paddingTop: 33.5,
fontSize: 17,
fontWeight: '600',
},
list: {
//justifyContent: 'space-around',
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
backgroundColor: '#FFFFFF'
},
row: {
backgroundColor: '#FFFFFF',
justifyContent: 'center',
width: (ScreenWidth - 1) / 4,
height: (ScreenWidth - 1) / 4,
alignItems: 'center',
// borderWidth: 0.5,
// borderRadius: 5,
// borderColor: '#E6E6E6'
},
sectionHeader: {
marginLeft: 10,
padding: 6.5,
fontSize: 12,
color: '#787878'
},
remark: {
margin: 10,
fontSize: 10,
color: '#D2D2D2',
marginBottom: 10,
alignSelf: 'center',
},
});
看下最終效果圖吧
最最后總結(jié)一下在開發(fā)中遇到的疑難雜癥還有sectionList的重要屬性。
不知道你有沒有遇見這個問題, 看起來很簡單, 應(yīng)該是我沒有引入Text組件, 但是我確實引入了。最終發(fā)現(xiàn)這個問題竟是因為我有段代碼是這樣寫的
<Image> source={require('../../assets/image/deadline.png')} style={styles.iconStyle} </Image>
<Text style={styles.userNameStyle}>賺積分,換好禮!</Text>
不知道你有沒有發(fā)現(xiàn)錯誤, 由于習(xí)慣我<Image>組件寫成<Image></Image>,Image是自封閉標簽所以
<Image source={require('../../assets/image/deadline.png')} style={styles.iconStyle} />
<Text style={styles.userNameStyle}>賺積分,換好禮!</Text>
這樣問題就解決了, 但是我不清楚它為啥會報這樣的錯,反正開發(fā)中總是會出現(xiàn)一些不知所以的錯, 所以平時寫代碼的時候不斷總結(jié)起來就好啦...
SectionList 屬性
ItemSeparatorComponent?: ?ReactClass<any>
行與行之間的分隔線組件。不會出現(xiàn)在第一行之前和最后一行之后。ListFooterComponent?: ?ReactClass<any>
尾部組件ListHeaderComponent?: ?ReactClass<any>
頭部組件keyExtractor: (item: Item, index: number) => string
此函數(shù)用于為給定的item生成一個不重復(fù)的key。Key的作用是使React能夠區(qū)分同類元素的不同個體,以便在刷新時能夠確定其變化的位置,減少重新渲染的開銷。若不指定此函數(shù),則默認抽取item.key作為key值。若item.key也不存在,則使用數(shù)組下標。onEndReached?: ?(info: {distanceFromEnd: number}) => void
當(dāng)所有的數(shù)據(jù)都已經(jīng)渲染過,并且列表被滾動到距離最底部不足onEndReachedThreshold個像素的距離時調(diào)用。onRefresh?: ?() => void
如果設(shè)置了此選項,則會在列表頭部添加一個標準的RefreshControl控件,以便實現(xiàn)“下拉刷新”的功能。同時你需要正確設(shè)置refreshing屬性。refreshing?: ?boolean
是否刷新嘍renderItem: (info: {item: Item, index: number}) => ?React.Element<any>
根據(jù)行數(shù)據(jù)data渲染每一行的組件。
除data外還有第二個參數(shù)index可供使用。renderSectionHeader?: ?(info: {section: SectionT}) => ?React.Element<any>
這就是我用到的每個section的組頭sections: Array<SectionT>
你的數(shù)據(jù)源
最后再提醒一下不要忘了key key key 哦 。
歡迎大家給提意見哦, 里面還是有一些不懂與不足的地方,快用你的見解砸我吧 ??