ReactNative的分組ListView-----SectionList

今天我要實(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 哦 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劝评,一起剝皮案震驚了整個(gè)濱河市姐直,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒋畜,老刑警劉巖声畏,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姻成,居然都是意外死亡插龄,警方通過查閱死者的電腦和手機(jī)愿棋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辫狼,“玉大人初斑,你說我怎么就攤上這事辛润∨虼Γ” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵砂竖,是天一觀的道長真椿。 經(jīng)常有香客問我,道長乎澄,這世上最難降的妖魔是什么突硝? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮置济,結(jié)果婚禮上解恰,老公的妹妹穿的比我還像新娘。我一直安慰自己浙于,他們只是感情好护盈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著羞酗,像睡著了一般腐宋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檀轨,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天胸竞,我揣著相機(jī)與錄音,去河邊找鬼参萄。 笑死卫枝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讹挎。 我是一名探鬼主播剃盾,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼淤袜!你這毒婦竟也來了痒谴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤铡羡,失蹤者是張志新(化名)和其女友劉穎积蔚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烦周,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尽爆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年怎顾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漱贱。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡槐雾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幅狮,到底是詐尸還是另有隱情募强,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布崇摄,位于F島的核電站擎值,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逐抑。R本人自食惡果不足惜鸠儿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厕氨。 院中可真熱鬧进每,春花似錦、人聲如沸命斧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯丙。三九已至肉瓦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胃惜,已是汗流浹背泞莉。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留船殉,地道東北人鲫趁。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像利虫,于是被迫代替她去往敵國和親挨厚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容