今天我要實(shí)現(xiàn)一個(gè) 類似于 iOS 開發(fā)中帶有分組的colllectionView 樣式的布局钩乍, 每個(gè)section都要有個(gè)組頭。
首先我們要先決定要使用什么控件怔锌。ScrollView和ListView/FlatList還有SectionList都是可以選擇的寥粹。
- ScrollView 會(huì)把所有子元素一次性全部渲染出來变过。使用上最簡單。但是如果你有一個(gè)特別長的列表需要顯示涝涤,可能會(huì)需要好幾屏的高度媚狰。這時(shí)就會(huì)占用很大的內(nèi)存去創(chuàng)建和渲染那些屏幕以外的JS組件和原生視圖,性能上也會(huì)有所拖累阔拳。
- ListView 更適用于長列表數(shù)據(jù)哈雏。它會(huì)惰性渲染子元素,并不會(huì)立即渲染所有元素衫生,而是優(yōu)先渲染屏幕上可見的元素裳瘪。
- FlatList 是0.43版本開始新出的改進(jìn)版的ListView,性能更優(yōu)罪针,但是官方說現(xiàn)在可能不夠穩(wěn)定彭羹,尚待時(shí)間考驗(yàn)。但是它不能夠分組/類/區(qū)(section)泪酱。
- SectionList 也是0.43版本推出的派殷, 高性能的分組列表組件。但是它不支持頭部吸頂懸浮的效果墓阀,但是也不要傷心毡惜,官方在下一個(gè)版本開始就可以支持懸浮的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ù)源溢十,每一個(gè)data 就是我們要用的item垮刹, renderItem就是你要顯示的子控件哦。如果你每個(gè)組都復(fù)用一個(gè)子組件那就按照例一的結(jié)構(gòu)张弛, 如果你想要不同的組返回不同樣式的子組件那就按照例二的結(jié)構(gòu)返回不同的renderItem即可荒典。
這里提個(gè)醒, key一定要有吞鸭, 不同的section 要設(shè)置不同的key才會(huì)渲染相應(yīng)的section寺董, 如果你key值都相同, 那可能會(huì)出現(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è)置他的樣式啦透敌。
接下來
這里我添加兩個(gè)屬性:
contentContainerStyle={styles.list}//設(shè)置cell的樣式
pageSize={4} // 配置pageSize確認(rèn)網(wǎng)格數(shù)量
const styles = StyleSheet.create({
list: {
//justifyContent: 'space-around',
flexDirection: 'row',//設(shè)置橫向布局
flexWrap: 'wrap', //設(shè)置換行顯示
alignItems: 'flex-start',
backgroundColor: '#FFFFFF'
},
});
好啦盯滚, 讓我們來看看效果。
??這是什么鬼酗电?魄藕??
為什么它的組頭也在左邊 撵术, 并且他的其他組數(shù)據(jù)都橫著了背率, 對(duì)于小白我來說只有大寫的懵~。不知道你們有沒有遇到這種情況嫩与, 是什么原因?qū)е碌模?我很是困惑啊寝姿, 當(dāng)我把
renderSectionHeader={this._renderSectionHeader}
這行代碼注掉的時(shí)候, 它的顯示是正常的...
這就尷尬了...
它的每一個(gè)小方塊是一個(gè)item划滋,達(dá)不到我要的效果啊饵筑, 于是我決定換個(gè)思路, 誰讓我是打不死的小白呢??
重新來
我決定讓每個(gè)section是一個(gè)item处坪。在每個(gè)item上創(chuàng)建多個(gè)可點(diǎn)擊的板塊根资。
_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映射, 這樣每個(gè)renderItem上返回了每一組我所需要的子組件同窘⌒粒快來看看我的變化吧??
腫么樣, 達(dá)到效果了吧想邦, 但是你有沒有發(fā)現(xiàn) 底部為啥是黃色的裤纹?,我可沒有去設(shè)置這么丑的顏色哦丧没,其實(shí)它是提醒我們有不完美的地方服傍, 下面就讓我們解決一下這個(gè)不完美吧 。
最后解決問題
最后讓我們來解決問題骂铁。它警告我們每個(gè)item 要有不同的key ,還記不記得我上面的提醒吹零,我也犯這個(gè)錯(cuò)誤了。
- 默認(rèn)情況下每行都需要提供一個(gè)不重復(fù)的key屬性拉庵。你也可以提供一個(gè)keyExtractor函數(shù)來生成key灿椅。
// 把這個(gè)屬性添加到 <SectionList/> 里面
keyExtractor = {this._extraUniqueKey}
_extraUniqueKey(item ,index){
return "index"+index+item;
}
這是每個(gè)item要設(shè)置key, 同樣每個(gè)子控件也不能放過, 一定要設(shè)置它的key钞支, 要不然這個(gè)屎黃色一直伴著你 多煩~~~
最后看一下我最終的代碼吧茫蛹!
var Dimensions = require('Dimensions');//獲取屏幕的寬高
var ScreenWidth = Dimensions.get('window').width;
var ScreenHeight = Dimensions.get('window').height;
// const AnimatedSectionList = Animated.createAnimatedComponent(SectionList);// 這個(gè)是創(chuàng)建動(dò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)建每個(gè)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ù)期收益非平臺(tái)承諾收益,市場有風(fēng)險(xiǎn)撼嗓,投資需謹(jǐn)慎</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è)置他的滑動(dòng)范圍
renderItem={this._renderItem}
ListFooterComponent={this._listFooterComponent}
ListHeaderComponent={this._listHeaderComponent}
renderSectionHeader={this._renderSectionHeader}
showsVerticalScrollIndicator={false}
keyExtractor = {this._extraUniqueKey}// 每個(gè)item的key
// contentContainerStyle={styles.list}
// horizontal={true}
// pageSize={4} // 配置pageSize確認(rèn)網(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的重要屬性柬采。
不知道你有沒有遇見這個(gè)問題欢唾, 看起來很簡單, 應(yīng)該是我沒有引入Text組件粉捻, 但是我確實(shí)引入了礁遣。最終發(fā)現(xiàn)這個(gè)問題竟是因?yàn)槲矣卸未a是這樣寫的
<Image> source={require('../../assets/image/deadline.png')} style={styles.iconStyle} </Image>
<Text style={styles.userNameStyle}>賺積分,換好禮肩刃!</Text>
不知道你有沒有發(fā)現(xiàn)錯(cuò)誤祟霍, 由于習(xí)慣我<Image>組件寫成<Image></Image>,Image是自封閉標(biāo)簽所以
<Image source={require('../../assets/image/deadline.png')} style={styles.iconStyle} />
<Text style={styles.userNameStyle}>賺積分盈包,換好禮沸呐!</Text>
這樣問題就解決了, 但是我不清楚它為啥會(huì)報(bào)這樣的錯(cuò)呢燥,反正開發(fā)中總是會(huì)出現(xiàn)一些不知所以的錯(cuò)垂谢, 所以平時(shí)寫代碼的時(shí)候不斷總結(jié)起來就好啦...
SectionList 屬性
- ItemSeparatorComponent?: ?ReactClass<any>
行與行之間的分隔線組件。不會(huì)出現(xiàn)在第一行之前和最后一行之后疮茄。
- ListFooterComponent?: ?ReactClass<any>
尾部組件
- ListHeaderComponent?: ?ReactClass<any>
頭部組件
- keyExtractor: (item: Item, index: number) => string
此函數(shù)用于為給定的item生成一個(gè)不重復(fù)的key滥朱。Key的作用是使React能夠區(qū)分同類元素的不同個(gè)體,以便在刷新時(shí)能夠確定其變化的位置力试,減少重新渲染的開銷徙邻。若不指定此函數(shù),則默認(rèn)抽取item.key作為key值畸裳。若item.key也不存在缰犁,則使用數(shù)組下標(biāo)。
- onEndReached?: ?(info: {distanceFromEnd: number}) => void
當(dāng)所有的數(shù)據(jù)都已經(jīng)渲染過怖糊,并且列表被滾動(dòng)到距離最底部不足onEndReachedThreshold個(gè)像素的距離時(shí)調(diào)用帅容。
- onRefresh?: ?() => void
如果設(shè)置了此選項(xiàng),則會(huì)在列表頭部添加一個(gè)標(biāo)準(zhǔn)的RefreshControl控件伍伤,以便實(shí)現(xiàn)“下拉刷新”的功能并徘。同時(shí)你需要正確設(shè)置refreshing屬性。
- refreshing?: ?boolean
是否刷新嘍
- renderItem: (info: {item: Item, index: number}) => ?React.Element<any>
根據(jù)行數(shù)據(jù)data渲染每一行的組件扰魂。
除data外還有第二個(gè)參數(shù)index可供使用麦乞。
- renderSectionHeader?: ?(info: {section: SectionT}) => ?React.Element<any>
這就是我用到的每個(gè)section的組頭
- sections: Array<SectionT>
你的數(shù)據(jù)源
最后再提醒一下不要忘了key key key 哦 。