React Native填坑之旅--ListView篇

列表顯示數據,基本什麼應用都是必須。筆者寫做的時候RN版本是0.34。今天就來從淺到深的看看React Native的ListView怎麼使用。react

首先是使用寫死的數據,以後會使用網絡請求的數據在界面中顯示。最後加上一個ActivityIndicator,網絡請求的過程當中顯示Loading圖標,加載完成以後顯示數據,隱藏Loading圖標。react-native

最簡單的

//@flow

import React from 'react';
import {
  Text,
  View,
  ListView
} from 'react-native';

export default class DemoList extends React.Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows(['row 1', 'row 2'])
    };
  }

  render() {
    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={(rowData) => <Text>{rowData}</Text>} />
    );
  }
}

引入所須要的內置組件之類的就很少說了。api

第一步,在constructor裏設置數據源,並同時指定何時從新繪製一行,就是在這個時候(r1, r2) => r1 !== r2} 重繪。網絡

以後,在state裏面設置數據源。下面從網絡請求數據的時候state的做用就更加明顯了。RN的組件在state發生改變的時候就會重繪。這個下面會詳細解釋。工具

最後,在render方法裏返回ListView,這裏的props裏有一個renderRow。在這裏指定的代碼就是把數據源中每一行的數據繪製在Text裏。fetch

一步一步接近實際產品開發

下面就把繪製行的部分抽象出來。在Native應用的開發中,不管是iOS仍是Android,行繪製的部分都是單獨出來的。在RN裏雖然能夠不獨立出來,可是你也看到了,這樣的寫法遇到稍微複雜一點的行內容的時候就捉襟見肘了。不獨立出來行繪製部分代碼會很難維護。flex

這部分不復雜,獨立出來之後是這樣的:this

import //...略...

export default class DemoList extends React.Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows(['row 1', 'row 2'])
    };

    //bind
    this._renderRow = this._renderRow.bind(this);
  }

  _renderRow(rowData) {
    return (
      <View style={{height: 50}}>
        <Text>{rowData}</Text>
      </View>
    );
  }

  render() {
    return (
      <ListView dataSource={this.state.dataSource}
        renderRow={this._renderRow} />
    );
  }
}

這個例子和上例基本上同樣。只是多了一個_renderRow(rowData)方法。code

注意:在使用這個方法之前,必定要綁定:this._renderRow = this._renderRow.bind(this);。綁定也能夠這樣<ListView dataSource={this.state.dataSource} renderRow={this._renderRow.bind(this)} />開發

在繪製行的時候,比以前稍微有一點改動。行文本的外面套了一個View,並指定這個View的高度爲50。

加上裝飾

從如今來看,數據只有兩行。若是不滑動一下的話,看起來和兩個上下排列的Text沒有什麼區別。

首先咱們加一個分割線:

export default class DemoList extends React.Component {
  constructor() {
    //記得使用方法以前綁定
    this._renderSeparator = this._renderSeparator.bind(this);
  }
  _renderRow(rowData) {
    // ...略...
  }

  _renderSeparator(sectionID: number, rowID: number, adjacentRowHighlighted: bool) {
    return (
      <View key={`{sectionID}-${rowID}`}
        style={{height: 1, backgroundColor: 'black'}}>
      </View>
    );
  }
  render() {
    return (
      <ListView dataSource={this.state.dataSource}
        renderRow={this._renderRow}
        renderSeparator={this._renderSeparator}
        />
    );
  }
}

這裏須要額外說明一些,在方法裏_renderSeparator(sectionID: number, rowID: number, adjacentRowHighlighted: bool)我看看到了在參數的名稱後面都有類型的說明。這個不是ES6的也不是js裏的,而是FB本身搞的一套靜態類型檢查工具裏的定義。這個工具叫Flow

若是你從一開始就沒打算跟flow扯上任何關係,那麼就按照ES標準寫就好。

至於分割線也是很是簡單。咱們這就返回了一個高度一個像素的,背景色爲黑色的view。

點擊和高亮

Row的點擊不想Native那樣,默認的通常就有了。在RN裏,咱們須要手動賦予一行能夠被點擊的功能。

_renderRow(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
    return (
      <TouchableHighlight onPress={() => {
        this._pressRow(rowID);
        highlightRow(sectionID, rowID);
      }}>
        <View style={styles.row}>
          <Text style={styles.text}>{rowData}</Text>
        </View>
      </TouchableHighlight>
    );
  }

在RN裏處理通常點擊的不二選擇就是TouchableHighlight。在TouchableHighlight裏的onPress裏調用自定義的_pressRow方法處理點擊,highlightRow方法高亮行。

固然,這裏就少不了用到樣式了:

const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    justifyContent: 'center',
    padding: 10,
    backgroundColor: '#F6F6F6',
  },
  text: {
    flex: 1,
  },
  seperator: {
    height: 1,
    backgroundColor: '#CCCCCC'
  }
});

把Cell分離

在實際的開發中,通常沒有人會把Row(或者行)的繪製和ListView放在一塊兒。咱們這裏就演示如何把Row的繪製分離出去。

首先建立一個單獨的文件,定義Cell:

import React from 'react';
import {
  View,
  Text,
  TouchableHighlight,
  StyleSheet
} from 'react-native';


export default class DemoCell extends React.Component {
  render() {
    return (
      <View>
        <TouchableHighlight onPress={this.props.onSelect}>
          <View style={styles.row}>
            <Text style={styles.text}>{this.props.rowData}</Text>
          </View>
        </TouchableHighlight>
      </View>
    );
  }
};

const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    justifyContent: 'center',
    padding: 10,
    backgroundColor: '#F6F6F6',
  },
  text: {
    flex: 1,
  },
});

Row也是一個組件,是一個組件就能夠在另外的組建裏渲染。因此,單獨定義的Row就是這麼用的。

回到demoList.js文件。在_renderRow方法中修改代碼:

_renderRow(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
    return (
      // <TouchableHighlight onPress={() => {
      //   this._pressRow(rowID);
      //   highlightRow(sectionID, rowID);
      // }}>
      //   <View style={styles.row}>
      //     <Text style={styles.text}>{rowData}</Text>
      //   </View>
      // </TouchableHighlight>
      <DemoCell onSelect={() => {
         this._pressRow(rowID);
         highlightRow(sectionID, rowID);
       }} rowData={rowData}/>
    );
  }

結合網絡請求

ListView在實戰中,除非是Settings之類的界面,數據都是從網絡請求獲得的。上一節中正好已經講述瞭如何使用RN內置的fetch請求網絡數據。這一節中就是用fetch來請求dribbble的數據。

在使用dribbble的數據以前你須要註冊,得到Access Token。這是請求認證所必須的。

export default class DemoList extends React.Component {

  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      isLoading: false,
      isLoadingTail: false,
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      filter: this.props.filter,
      queryNumber: 0,
    };
    //...略...
  }

  //...略...

  _getShots(query: string) {
    this.setState({
      isLoading: true,
      queryNumber: this.state.queryNumber + 1,
      isLoadingTail: false,
    });

    api.getShotsByType(query, 1).then((responseData) => {
      this.setState({
        isLoading: false,
        dataSource: this._getDataSource(responseData),
      });
    }).catch((error) => {
      this.LOADING[query] = false;
      this.resultsCache.dataForQuery[query] = undefined;

      this.setState({
          dataSource: this._getDataSource([]),
          isLoading: false,
      });
    });
  }

仍是在類DemoList裏,其餘可有可無的代碼先略去。要緊的地方是須要注意在constructor裏設置state的時候dataSource如何設置的。

state的改變會影響到組件的繪製。因此,在_getShots方法裏,開始請求以前先設置一個默認的state狀態。在請求成功以後使用setState設置一個,在catch到異常的時候再顯示另一個。

state裏還有一個屬性叫作isLoading: false,。這個是影控制Loading視圖的。在false的時候隱藏,在true的時候顯示。

那麼loading界面是什麼樣呢?

<View style={{alignItems: 'center', justifyContent: 'center', flex: 1, backgroundColor: 'white'}}>
    <ActivityIndicator animating={true}
      style={[styles.centering]}
      size="large"
      color="#cccccc"
    />
</View>

組合起來

在類DemoList裏組合相關代碼:

_renderView() {
    if (this.state.isLoading) {
      return (
        <UNActivityIndicator loadingType={LOADING_TYPE.Large} />
      );
    }

    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this._renderRow}
          renderSeparator={this._renderSeparator}
          automaticallyAdjustContentInsets={false}
          />
      </View>
    );
  }

在renderView的時候,先檢查state.isLoading,若是須要loading視圖,那麼返回loading視圖,其餘的不返回。數據加載成功以後state.isLoading被設置爲false,那麼顯示ListView。

填坑完畢

以上就是處理ListView和其中的Cell的一些常見問題的方法。

相關文章
相關標籤/搜索