React Native可伸縮列表封裝與快速實現(xiàn)方案


ReactNative.png

類似QQ好友列表的可伸縮分組列表璧函,相信對于一部分的開發(fā)者來說還是有需要用到的锨并,本文將會結(jié)合自己的開發(fā)理解和已發(fā)布在github和npm上的實例精刷,結(jié)合講解如何使用React Native快速封裝此類組件拒啰。你也可以查看已經(jīng)封裝并發(fā)布在Github上的組件react-native-expandable-section-list查看效果斑唬,當(dāng)然在你的項目中,你也可以直接使用該組件肌蜻,如發(fā)現(xiàn)任何bug請及時提出issue給我互墓,我會認真處理,如果你覺得組件做的還不錯蒋搜,請給我一個star哦篡撵!

封裝思路解析


原生ios開發(fā)階段時,UITableView功能強大豆挽,實現(xiàn)類似QQ列表這樣的功能育谬,你只需要在UITableView的delegate方法中作相關(guān)控制即可實現(xiàn),但是最開始轉(zhuǎn)到react native的時候帮哈,由于當(dāng)時剛參加工作膛檀,沒什么思路,后經(jīng)過一些嘗試和封裝,才漸漸有了現(xiàn)在的實現(xiàn)方法

  • 思路一: 通過View的onLayout回調(diào)記錄分組布局大小宿刮,控制打開組高度和關(guān)閉組高度實現(xiàn)類似開關(guān)分組效果(這個思路和代碼是一個同事的方法,代碼稍微有點繞私蕾,但是可以控制打開關(guān)閉的動畫僵缺,我暫時放棄了使用,但是在寫下一篇文章會來講解這個方法的思路和實現(xiàn))

  • 思路二: 控制數(shù)據(jù)踩叭,改變開關(guān)分組的標記值磕潮,并結(jié)合react native的state渲染實現(xiàn)類似開關(guān)分組效果(這是當(dāng)前用在實際項目中的一個組件,下面會具體介紹如何實現(xiàn)它的代碼和思路)

代碼解析


首先渲染一個全屏的ListView容贝,ListView中的row當(dāng)作分組自脯,row渲染成下列組件:

section.png
  • 分組渲染:
_renderRow = (rowData, sectionId, rowId) => { // eslint-disable-line
    const { renderRow, renderSectionHeaderX, renderSectionFooterX, headerKey, memberKey } = this.props;
    let memberArr = rowData[memberKey];
    if (!this.state.memberOpened.get(rowId) || !memberArr) {
      memberArr = [];
    }

    return (
      <View>
        <TouchableOpacity onPress={() => this._onPress(rowId)}>
          { renderSectionHeaderX ? renderSectionHeaderX(rowData[headerKey], rowId) : null}
        </TouchableOpacity>
        <ScrollView scrollEnabled={false}>
          {
            memberArr.map((rowItem, index) => {
              return (
                <View key={index}>
                  {renderRow ? renderRow(rowItem, index, sectionId) : null}
                </View>
              );
            })
          }
          { memberArr.length > 0 && renderSectionFooterX ? renderSectionFooterX(rowData, sectionId) : null }
        </ScrollView>
      </View>
    );
  }

說明:如代碼,每個Row中都由renderSectionHeaderX斤富、renderRow膏潮、renderSectionFooterX組成,renderSectionHeaderX通過Touchable組件的點擊事件满力,可以控制下面的ScrollView包著的一個個組成員renderRow焕参,及sectionFooter,每次渲染這樣一個分組的時候通過當(dāng)前組的開關(guān)狀態(tài)this.state.memberOpened來獲得當(dāng)前memberArr數(shù)組是空數(shù)組或是你的傳入數(shù)據(jù)rowData[memberKey],當(dāng)然也可以控制你的關(guān)閉狀態(tài)不一定是空數(shù)組油额,這里的開關(guān)狀態(tài)寄存器是一個Map對象叠纷,或者你也可以自己構(gòu)造合適的鍵值對對象

  • 開關(guān)對象構(gòu)造:
 constructor(props) {
    super(props);
    this.ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
    let map = new Map();
    if (props.dataSource && props.isOpen) {
      props.dataSource.map((item, i) => map.set(i.toString(), true))
    }

    if (props.openOptions) {
      props.openOptions.map((item) => map.set(item.toString(), true))
    }
    this.state = {
      memberOpened: map
    }
  }

說明:構(gòu)造狀態(tài)寄存器,通過屬性設(shè)置默認的分組開關(guān)狀態(tài)潦嘶,isOpenbool值涩嚣,記錄是否全部打開分組, openOptions是一個數(shù)組,里面記錄你選擇打開哪些分組掂僵,如打開0, 2分組航厚,即為[0,2]

  • 點擊開關(guān)方法:
_onPress = (i) => {
    this.setState((state) => {
      const memberOpened = new Map(state.memberOpened);
      memberOpened.set(i, !memberOpened.get(i)); // toggle
      return { memberOpened };
    });

    if (this.props.headerOnPress) {
      this.prop.headerOnPress(i, this.state.memberOpened.get(i) || false);
    }

    LayoutAnimation.easeInEaseOut();
  };

說明:每次點擊組頭,執(zhí)行該方法锰蓬,改變當(dāng)前組在狀態(tài)寄存器中設(shè)置的狀態(tài)阶淘,最開始思路甚至把這狀態(tài)寄存設(shè)置成為每個分組數(shù)據(jù)的某個特定屬性,后來發(fā)現(xiàn)Map拿來這里當(dāng)作一種寄存器用真的很完美互妓。

  • 數(shù)據(jù)源
 const MockData = [
        ...
        {
            header: 'sectionHeader',
            member: [
            ...
                {
                    title: 'memberTitle',
                    content: 'content',
                },
            ...
            ]
        },
        ...
    ]

特定組件是拿來做特定用途的溪窒,react-native-expandable-section-list只適用于本文說明的情況,當(dāng)然冯勉,也對數(shù)據(jù)源做一定的限制澈蚌,從而快速封裝出來QQ列表的可伸縮分組效果。

FlatList擴展封裝


react native在版本0.43后提出使用FlatList灼狰,在使用FlatList的時候宛瞄,通過同樣的思路,也對FlatList做了類似的擴展交胚,組件可見react-native-expandable-section-flatlist份汗,0.43版本后的FlatList確實是對列表組件性能做了極大的提升盈电,數(shù)據(jù)源data屬性,加上擴展數(shù)據(jù)屬性extraData的使用讓每次的state組件渲染不會再是渲染當(dāng)前列表杯活,而是直接定位到你作出改變的分組從而改變分組的開關(guān)狀態(tài)匆帚。keyExtractor屬性也更加利于定位每一個分組的位置和設(shè)定。在數(shù)據(jù)測試時旁钧,暫只做了100個分組的測試吸重,F(xiàn)latList的性能原理是只會顯示你看到的這部分分組,做的還是相當(dāng)好的歪今,暫未發(fā)現(xiàn)性能影響嚎幸。我盡量減少了對原組件FlatList的屬性影響,其他類似上拉刷新寄猩,下拉加載等屬性也全部兼容嫉晶,接下來直接展示精簡后的代碼:

class ExpanableList extends Component {
  constructor(props) {
    super(props);
    let map = new Map();
    if (props.dataSource && props.isOpen) {
      props.dataSource.map((item, i) => map.set(i, true))
    }

    if (props.openOptions) {
      props.openOptions.map((item) => map.set(item, true))
    }
    this.state = {
      memberOpened: map
    }
  }

  static propTypes = {
    dataSource: PropTypes.array.isRequired,
    headerKey: PropTypes.string,
    memberKey: PropTypes.string,
    renderRow: PropTypes.func,
    renderSectionHeaderX: PropTypes.func,
    renderSectionFooterX: PropTypes.func,
    headerOnPress: PropTypes.func,
    isOpen: PropTypes.bool,
    openOptions: PropTypes.array,
  };

  static defaultProps = {
    headerKey: 'header',
    memberKey: 'member',
    isOpen: false,
  };

  _keyExtractor = (item, index) => index;

  _onPress = (i) => {
    this.setState((state) => {
      const memberOpened = new Map(state.memberOpened);
      memberOpened.set(i, !memberOpened.get(i)); // toggle
      return { memberOpened };
    });

    if (this.props.headerOnPress) {
      this.prop.headerOnPress(i, this.state.memberOpened.get(i) || false);
    }

    LayoutAnimation.easeInEaseOut();
  };

  _renderItem = ({ item, index }) => { // eslint-disable-line
    const { renderRow, renderSectionHeaderX, renderSectionFooterX, headerKey, memberKey } = this.props;
    const sectionId = index;
    let memberArr = item[memberKey];
    if (!this.state.memberOpened.get(sectionId) || !memberArr) {
      memberArr = [];
    }

    return (
      <View>
        <TouchableOpacity onPress={() => this._onPress(sectionId)}>
          { renderSectionHeaderX ? renderSectionHeaderX(item[headerKey], sectionId) : null}
        </TouchableOpacity>
        <ScrollView scrollEnabled={false}>
          {
            memberArr.map((rowItem, rowId) => {
              return (
                <View key={rowId}>
                  {renderRow ? renderRow(rowItem, rowId, index) : null}
                </View>
              );
            })
          }
          { memberArr.length > 0 && renderSectionFooterX ? renderSectionFooterX(item, sectionId) : null }
        </ScrollView>
      </View>
    );
  };

  render() {
    const { dataSource } = this.props;
    return (
      <FlatList
        {...this.props}
        data={dataSource}
        extraData={this.state}
        keyExtractor={this._keyExtractor}
        renderItem={this._renderItem}
      />
    );
  }
}

寫在最后


react-native-expandable-section-listreact-native-expandable-section-flatlist封裝的相對簡單,但是還是很實用的田篇,一點小經(jīng)驗分享希望能對你有所幫助车遂。

寫總結(jié)和分享文章確實是一件快樂的事情,第一篇文章React Native路由理解和react-navigation庫封裝學(xué)習(xí)受到了一些人的肯定斯辰,真的很開心舶担,謝謝各位,一起進步1蛏搿衣陶!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闸氮,隨后出現(xiàn)的幾起案子剪况,更是在濱河造成了極大的恐慌,老刑警劉巖蒲跨,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件译断,死亡現(xiàn)場離奇詭異,居然都是意外死亡或悲,警方通過查閱死者的電腦和手機孙咪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巡语,“玉大人翎蹈,你說我怎么就攤上這事∧泄” “怎么了荤堪?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我澄阳,道長拥知,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任碎赢,我火速辦了婚禮低剔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揩抡。我一直安慰自己户侥,他們只是感情好镀琉,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布峦嗤。 她就那樣靜靜地躺著,像睡著了一般屋摔。 火紅的嫁衣襯著肌膚如雪烁设。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天钓试,我揣著相機與錄音装黑,去河邊找鬼。 笑死弓熏,一個胖子當(dāng)著我的面吹牛恋谭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挽鞠,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼疚颊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了信认?” 一聲冷哼從身側(cè)響起材义,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嫁赏,沒想到半個月后其掂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡潦蝇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年款熬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攘乒。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡华烟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出持灰,到底是詐尸還是另有隱情盔夜,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站喂链,受9級特大地震影響返十,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椭微,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一洞坑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝇率,春花似錦迟杂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锅尘,卻和暖如春监氢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藤违。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工浪腐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顿乒。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓议街,卻偏偏與公主長得像,于是被迫代替她去往敵國和親璧榄。 傳聞我的和親對象是個殘疾皇子特漩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

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

  • React Native優(yōu)秀博客,以及優(yōu)秀的Github庫列表(很多英文資料源自于[awesome-react-n...
    董董董董董董董董董大笨蛋閱讀 10,596評論 4 162
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協(xié)議犹菱。它實...
    香橙柚子閱讀 23,796評論 8 183
  • 人活在世上拾稳,最可貴的是什么?我想腊脱,不過是真善美访得,而最美始于真。 但人能活在世上陕凹,最基本的是什么悍抑?很多人說,是厚黑學(xué)...
    Snookerycz閱讀 470評論 0 1
  • 很多時候杜耙,人們喜歡后悔搜骡,喜歡回憶過去,喜歡感受自己曾經(jīng)擁有的東西佑女。而在自己擁有的時候卻毫不在意记靡。我知道這些道理很...
    禾衍閱讀 183評論 0 1
  • 我上大學(xué)的地方摸吠,有著一片浩瀚的星空空凸。也許是因為旁邊是機場,建筑物不能起的太高寸痢,平時都不能放煙花呀洲,浩瀚的星空變成了我...
    Onion_萌蔥頭閱讀 1,328評論 0 2