React Native——使用SectionList改造電影列表

不變旋律發表於2019-03-04

在上篇文章《ReactNative——使用FlatList實現豆瓣電影列表》中我們用FlatList實現了一個豆瓣電影的列表頁。本篇我們使用SectionList將兩個電影列表頁改為一個分組的列表頁,學習一下SectionList的用法。

SectionList是用於多個分組的列表控制元件,如果你的列表不需要分組,那還是用FlatList。SectionList的資料來源屬性是sections,其作用與FlatList的data屬性一樣,都是給列表設定資料來源,但兩個屬性有所區別,需要注意。

sections是一個只讀的陣列,在渲染分組列表之前我們就需要設定好其中的資料。這個陣列中的元素可以設定為一個字典型別{key1:value1, key2:value2, …},但是data欄位和值是必須的,也就是說其資料格式通通常可以寫成下面的形式:

[
  {data: {obj1, obj2, obj3, ...}, key1: value1, key2: value2, ...},
  {data: {obj1, obj2, obj3, ...}, key1: value1, key2: value2, ...},
];
複製程式碼

data欄位中的資料是每個section的資料來源,是必不可少的,其它key、value可以根據實際情況自己設定。比如renderSectionHeader函式中我們想區分當前渲染的是哪一個section,可以在section的資料來源中為每條資料加入一個index:

[
  {data: {obj1, obj2, obj3, ...}, index: 0},
  {data: {obj1, obj2, obj3, ...}, index: 1},
];
複製程式碼

這樣在取值的時候我們可以根據index來判斷當前是哪個section並做不同的渲染。section的資料構造是非常靈活的,可以根據實際需要來構造不同的資料。

下面我們來看看怎樣改造電影列表。我們先還是構造state中的資料,如下:

constructor(props) {
    super(props);
    this.state = {
      displayingMovies: [],  // 正在上映的電影資料
      incomingMovies: [],    // 即將上映的電影資料
      sectionData: [],      // SectionList資料來源
      loaded: false,  // 用來控制loading檢視的顯示,當資料載入完成,loading檢視不再顯示
    };
  }
複製程式碼

render函式中的FlatList要改為SectionList了:

render() {
   if (!this.state.loaded) {
     return (
       <View style={styles.loadingView}>
         <ActivityIndicator animating={true} size="small"/>
         <Text style={{color: `#666666`, paddingLeft: 10}}>努力載入中</Text>
       </View>
     )
   }
   return (
     <SectionList
       keyExtractor={this._keyExtractor}
       renderSectionHeader={this._renderSectionHeader}
       renderItem={this._renderItem}
       sections={this.state.sectionData}
     />
   )
 }
複製程式碼

多了一個renderSectionHeader屬性,我們要用它來渲染一個粘性的標題元件:

_renderSectionHeader = (item) => {
  let sectionObj = item.section;
  let sectionIndex = sectionObj.index;
  let title = (sectionIndex === 0) ? "正在上映" : "即將上映";
  return (
    <View style={styles.sectionHeader}>
      <Text style={styles.sectionTitle}>{title}</Text>
    </View>
  )
};
複製程式碼

這裡就用到了index來區分section,index是我在section的資料來源陣列中設定的一個值,下面來看看介面部分的呼叫邏輯。我們有兩個介面需要呼叫,這兩個介面都是非同步呼叫的,我們需要得到兩個列表資料displayingMovies和incomingMovies,並對這兩個陣列進行處理,構造sectionData資料。

這裡我修改了一下介面呼叫的邏輯,分三步:

  1. 先獲取正在上映的電影資料,得到displayingMovies;
/**
 * 先載入正在上映的電影列表,如果載入成功,接著獲取即將上映的電影資料
 */
loadDisplayingMovies() {
  let that = this;
  fetch(queryMovies(`北京`, 0, 20)).then((response) => response.json()).then((json) => {
    console.log(json);
    let movies = [];
    for (let idx in json.subjects) {
      let movieItem = json.subjects[idx];
      let directors = ""; // 導演
      for (let index in movieItem.directors) {
        // 得到每一條電影的資料
        let director = movieItem.directors[index];
        // 將多個導演的名字用空格分隔開顯示
        if (directors === "") {
          directors = directors + director.name
        } else {
          directors = directors + " " + director.name
        }
      }
      movieItem["directorNames"] = directors;
      
      // 拼裝主演的演員名字,多個名字用空格分隔顯示
      let actors = "";
      for (let index in movieItem.casts) {
        let actor = movieItem.casts[index];
        if (actors === "") {
          actors = actors + actor.name
        } else {
          actors = actors + " " + actor.name
        }
      }
      movieItem["actorNames"] = actors;
      movies.push(movieItem)
    }
    // 注意此處setState的寫法,表示setState完成後再呼叫loadComingMovies()方法,此時state中的資料已經完成更新了。
    that.setState(
      {
        displayingMovies: movies,
      },
      () => {
        // 載入完正在上映的電影后再接著載入即將上映的電影資料
        that.loadComingMovies();
      }
    )
  }).catch((e) => {
    console.log("載入失敗");
    that.setState({
      loaded: true
    })
  }).done();
}
複製程式碼
  1. 獲取成功後接著呼叫介面獲取即將上映的電影資料,得到incomingMovies;
  2. 兩個資料都拿到後再更新sectionData,介面重新整理後資料就顯示出來了。
/**
 * 載入即將上映的電影列表,並更新sectionData重新整理列表
 */
loadComingMovies() {
  let that = this;
  fetch(comingMovies(`北京`, 0, 20)).then((response) => response.json()).then((json) => {
    console.log(json);
    if (json == null) {
      that.setState({
        loaded: true,
      });
      return
    }
    let movies = [];
    for (let idx in json.subjects) {
      let movieItem = json.subjects[idx];
      let directors = "";
      for (let index in movieItem.directors) {
        let director = movieItem.directors[index];
        if (directors === "") {
          directors = directors + director.name
        } else {
          directors = directors + " " + director.name
        }
      }
      movieItem["directorNames"] = directors;
      
      let actors = "";
      for (let index in movieItem.casts) {
        let actor = movieItem.casts[index];
        if (actors === "") {
          actors = actors + actor.name
        } else {
          actors = actors + " " + actor.name
        }
      }
      movieItem["actorNames"] = actors;
      movies.push(movieItem)
    }
    // 兩個電影資料都載入完成後需要更新sectionData,將資料在介面上顯示出來
    let sectionList = [
      {data: that.state.displayingMovies, index: 0},
      {data: movies, index: 1},
    ];
    that.setState({
      loaded: true,
      incomingMovies: movies,
      sectionData: sectionList
    });
  }).catch((error) => {
    console.log("載入失敗");
    that.setState({
      loaded: true
    })
  }).done();
}
複製程式碼

這樣我們就完成了資料的載入,介面也成功重新整理出來,以下是改造之後的效果圖:

React Native——使用SectionList改造電影列表

SectionList的使用關鍵就是弄清楚sections這個屬性,怎樣構造資料來源資料,在使用時取值還要注意,多除錯一步步弄清楚每個section,每個item中資料結構是怎樣的,避免出錯。

Demo地址: github.com/mrarronz/re…

相關文章