| 版權聲明:本文爲博主原創文章,未經博主允許不得轉載。
一、內容簡介
對於擁有國外市場或者通訊功能的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',
}
});
- 注:核心思路來源於簡書上的一篇文章,在此基礎之上進行封裝改造
- 文章鏈接:http://www.jianshu.com/p/cec3bf7ed7b1