《ReactNative系列講義》進階篇---02.自定義機場選擇列表 AirportListView

| 版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

一、內容簡介

對於擁有國外市場或者通訊功能的APP來說,通過列表展示選擇自己需要的數據是很常見的功能模塊,例如:通訊錄,選擇國家等等。這類組件的實現原理基本一致,本篇文章帶大家實現一個機場的列表選擇模塊。
目前只實現了最基礎的首字母定位查找功能,首字母分組功能,後期會加上模糊查詢,熱門城市推薦。

技術點總結
  • ReactNative 原生ListView組件
  • 查詢並存儲所有選項的首字母,並且去重
  • 將城市名稱按照首字母分組
  • 記錄每個分組起始的高度
  • 異步讀取本地數據

二、代碼實現

1、數據源數據結構

數據片段

{
  "allAirportList":[
    {
        "airport": "伊爾施",
        "city": "阿爾山",
        "enAirport": "",
        "match": "阿爾山 伊爾施|aershanyiershi|YIE",
        "pinyin": "aershanyiershi",
        "tcode": "YIE",
        "sortLetters": "a"
    },
    {
        "airport": "阿克蘇",
        "city": "阿克蘇",
        "enAirport": "",
        "match": "阿克蘇 阿克蘇|akesu|AKU",
        "pinyin": "akesu",
        "tcode": "AKU",
        "sortLetters": "a"
    },
    {
        "airport": "阿勒泰",
        "city": "阿勒泰",
        "enAirport": "",
        "match": "阿勒泰 阿勒泰|aletai|AAT",
        "pinyin": "aletai",
        "tcode": "AAT",
        "sortLetters": "a"
    },
    {
        "airport": "昆莎",
        "city": "阿里",
        "enAirport": "",
        "match": "阿里 昆莎|alikunsha|NGQ",
        "pinyin": "alikunsha",
        "tcode": "NGQ",
        "sortLetters": "a"
    },
  ]
}
2、實現ListView基礎功能
  • 創建AirportListView類
  • 初始化ListView,這裏用到了ListView的Section功能,詳情可查看官網Demo
  • 定義狀態機
import React, {Component,PropTypes}from 'react';
import  {
  StyleSheet,
  View,
  Text,
  Platform,
  TouchableOpacity,
  ListView,
  Dimensions,
} from 'react-native';
export default class AirportListView extends Component {
    constructor(props) {
       super(props);
       this.totalHeight = []; // 存放每個分組的起始高度

       var getSectionData = (dataBlob, sectionID) => {
         return sectionID;
       };
       var getRowData = (dataBlob, sectionID, rowID) => {
         return dataBlob[sectionID][rowID];
       };

       let ds = new ListView.DataSource({
         getRowData: getRowData,
         getSectionHeaderData: getSectionData,
         rowHasChanged: (row1, row2) => row1 !== row2,
         sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
       });

       this.state = {
         citiesData: ds.cloneWithRowsAndSections([])
         ,letters: []
       };
    }
}
3、數據處理
  • 獲取源數據
  • 分揀出涉及到的首字母
  • 將城市數據按首字母索引分組
  • 計算每個分組的高度並存儲
  • 異步處理數據
(1) 獲取數據源

數據源由調用該組件的頁面傳遞

let data = this.props.dataSource;
(2) 獲取字母索引數組
let letterList = this._getSortLetters(data);
  • for循環的大意:拿到數據列表中每個對象的sortLetter字段,首先小寫轉化成大寫,其次遍歷字母索引數組,遍歷過程中拿前一步獲取到字段進行比較,如果有相等的項,說明此字母已出現過,不必做存儲,中斷循環,跳入下一次的遍歷,如果不存在相等項,則將該字母存入索引數組,最後返回字母索引數組
_getSortLetters(dataList) {
    let list = []; // 存放數據數組

    for (let i = 0; i < dataList.length; i++) {
      let sortLetters = dataList[i].sortLetters.toUpperCase();

      let exist = false;
      for (let j = 0; j < list.length; j++) {
        if (list[j] === sortLetters) {
            exist = true;
        }
        if (exist) {
            break;
        }
      }
      if (!exist) {
          list.push(sortLetters);
      }
    }

    return list;
  }
(3)將城市數據按首字母索引分組
  • dataBlob中存放的是按照首字母分好組的城市數據
 let dataBlob = {};
 data.map(cityJson => {
   let key = cityJson.sortLetters.toUpperCase();

   if (dataBlob[key]) {
     let subList = dataBlob[key];
     subList.push(cityJson);
   } else {
     let subList = [];
     subList.push(cityJson);
     dataBlob[key] = subList;
   }
 });
(4) 計算每個分組的高度並存儲
  • 通過Object.keys(dataBlog)獲取到所有的key值,也就是所有分組的header
  • 提煉出每個組所包含的子元素總數
  • 計算出每個section的總高度:header高度 + row高度 * row每個組的總個數
 let sectionIDs = Object.keys(dataBlob);
 let rowIDs = sectionIDs.map(sectionID => {
   let thisRow = [];
   let count = dataBlob[sectionID].length;
   for (let i = 0; i < count; i++) {
     thisRow.push(i);
   }

   let eachheight = SECTION_HEIGHT + ROW_HEIGHT * thisRow.length;
   this.totalHeight.push(eachheight);

   return thisRow;
 });
(5) 添加異步功能
groupingData() {
    return new Promise((resolve, reject) => {
     let data = this.props.dataSource;
     let letterList = this._getSortLetters(data);
     let dataBlob = {};

     // 將城市按字母索引分組
     data.map(cityJson => {
       let key = cityJson.sortLetters.toUpperCase();

       if (dataBlob[key]) {
         let subList = dataBlob[key];
         subList.push(cityJson);
       } else {
         let subList = [];
         subList.push(cityJson);
         dataBlob[key] = subList;
       }
     });

     let sectionIDs = Object.keys(dataBlob);
     let rowIDs = sectionIDs.map(sectionID => {
       let thisRow = [];
       let count = dataBlob[sectionID].length;
       for (let i = 0; i < count; i++) {
         thisRow.push(i);
       }

       let eachheight = SECTION_HEIGHT + ROW_HEIGHT * thisRow.length;
       this.totalHeight.push(eachheight);

       return thisRow;
     });

     let result = {
       dataBlob: dataBlob
       ,sectionIDs: sectionIDs
       ,rowIDs: rowIDs
     }

     resolve(result);
    });
  }
4、佈局顯示
  • 主佈局
  • 字母索引布局
  • ListView定位方法
  • 點擊響應方法

主佈局

render() {
    return (
      <View style={{flexDirection: 'column',backgroundColor: '#F4F4F4'}}>
        <View style={{height: Dimensions.get('window').height,marginBottom: 10}}>
          <ListView
            ref={listView => this._listView = listView}
            contentContainerStyle={{flexDirection: 'row',width: width,backgroundColor: 'white',justifyContent: 'space-around',flexWrap: 'wrap',}}
            dataSource={this.state.citiesData}
            renderRow={this._renderListRow}
            renderSectionHeader={this._renderListSectionHeader}
            enableEmptySections={true}
            initialListSize={1000}
          />
          <View style={styles.letters}>
            {this.renderLetters()}
          </View>
        </View>
      </View>
    )
  }

sectionheader佈局

_renderListSectionHeader(sectionData, sectionID) {
    return (
      <View style={{paddingTop: 5,paddingBottom: 5,height: 30,paddingLeft: 10,width: width,backgroundColor: '#F4F4F4',}}>
        <Text style={{color: '#333333',fontWeight: 'bold'}}>
          {sectionData}
        </Text>
      </View>
    );
  }

row佈局

  • 構造方法中獲取this
constructor(props) {
    super(props);
    that = this;
}
_renderListRow(cityJson, rowId) {
    return (
      <TouchableOpacity
        key={'list_item_' + cityJson.tcode}
        style={{height: ROW_HEIGHT,paddingLeft: 10,paddingRight: 10,borderBottomColor: '#F4F4F4',borderBottomWidth: 1,}}
        onPress={()=> {
          that._cityNameClick(cityJson)
        }}>
        <View style={{ paddingTop: 10, paddingBottom: 2 }}>
          <Text style={{color: '#333333',width: width}}>{cityJson.city} ({cityJson.tcode})</Text>
        </View>
      </TouchableOpacity>
    )
  }

字母索引布局

  • 這裏用到了第三方數據操作框架underscorejs
renderLetters() {
    if(!_.isEmpty(this.state.letters)) {
      return(
        <View>
          {this.state.letters.map((letter, index) => this._renderRightLetters(letter, index))}
        </View>
      )
    }
  }

索引定位

  // 根據字母索引定位城市列表
  _scrollTo(index, letter) {
    let position = 0;
    for (let i = 0; i < index; i++) {
      position += this.totalHeight[i]
    }
    this._listView.scrollTo({
      y: position
    });
  }

點擊響應方法callback

  • 類中定義回調函數類型
static propTypes = {
    onConfirm: PropTypes.func.isRequired,
  };

// 選擇城市後的callback
_cityNameClick(cityJson) {
    this.props.onConfirm(cityJson);
}

樣式

const styles = StyleSheet.create({
    letters: {
        position: 'absolute',
        height: height,
        top: 0,
        bottom: 0,
        right: 10,
        backgroundColor: 'transparent',
        justifyContent: 'flex-start',
        alignItems: 'flex-start',
    },
    letter: {
        height: height * 3.3 / 100,
        width: width * 3 / 50+10,
        justifyContent: 'center',
        alignItems: 'center',
    }
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章