城市列表选择是很多app共有的功能,比如典型的美图app。那么对于React Native怎么实现呢?
要实现上面的效果,首先需要对界面的组成简单分析,界面的数据主要由当前城市,历史访问城市和热门城市组成,所以我们在提供Json数据的时候就需要将数据分为至少3部分。
const ALL_CITY_LIST = DATA_JSON.allCityList; const HOT_CITY_LIST = DATA_JSON.hotCityList; const LAST_VISIT_CITY_LIST = DATA_JSON.lastVisitCityList;
而要实现字母索引功能,我们需要自定义一个控件,实现和数据的绑定关系,自定义组件代码如下:
CityIndexListView.js
'use strict'; import React, {Component} from 'react'; import { StyleSheet, View, Text, TouchableOpacity, ListView, Dimensions, } from 'react-native'; import Toast, {DURATION} from './ToastUtil' const SECTIONHEIGHT = 30; const ROWHEIGHT = 40; const ROWHEIGHT_BOX = 40; var totalheight = []; //每个字母对应的城市和字母的总高度 const {width, height} = Dimensions.get('window'); var that; const key_now = '当前'; const key_last_visit = '最近'; const key_hot = '热门'; export default class CityIndexListView extends Component { constructor(props) { super(props); var getSectionData = (dataBlob, sectionID) => { return sectionID; }; var getRowData = (dataBlob, sectionID, rowID) => { return dataBlob[sectionID][rowID]; }; let ALL_CITY_LIST = this.props.allCityList; let CURRENT_CITY_LIST = this.props.nowCityList; let LAST_VISIT_CITY_LIST = this.props.lastVisitCityList; let HOT_CITY_LIST = this.props.hotCityList; let letterList = this._getSortLetters(ALL_CITY_LIST); let dataBlob = {}; dataBlob[key_now] = CURRENT_CITY_LIST; dataBlob[key_last_visit] = LAST_VISIT_CITY_LIST; dataBlob[key_hot] = HOT_CITY_LIST; ALL_CITY_LIST.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 ii = 0; ii < count; ii++) { thisRow.push(ii); } let eachheight = SECTIONHEIGHT + ROWHEIGHT * thisRow.length; if (sectionID === key_hot || sectionID === key_now || sectionID === key_last_visit) { let rowNum = (thisRow.length % 3 === 0) ? (thisRow.length / 3) : parseInt(thisRow.length / 3) + 1; console.log('sectionIDs===>' + sectionIDs + ", rowNum=====>" + rowNum); eachheight = SECTIONHEIGHT + ROWHEIGHT_BOX * rowNum; } totalheight.push(eachheight); return thisRow; }); let ds = new ListView.DataSource({ getRowData: getRowData, getSectionHeaderData: getSectionData, rowHasChanged: (row1, row2) => row1 !== row2, sectionHeaderHasChanged: (s1, s2) => s1 !== s2 }); this.state = { dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs), letters: sectionIDs }; that = this; } _getSortLetters(dataList) { let list = []; for (let j = 0; j < dataList.length; j++) { let sortLetters = dataList[j].sortLetters.toUpperCase(); let exist = false; for (let xx = 0; xx < list.length; xx++) { if (list[xx] === sortLetters) { exist = true; } if (exist) { break; } } if (!exist) { list.push(sortLetters); } } return list; } _cityNameClick(cityJson) { // alert('选择了城市====》' + cityJson.id + '#####' + cityJson.name); this.props.onSelectCity(cityJson); } _scrollTo(index, letter) { this.refs.toast.close(); let position = 0; for (let i = 0; i < index; i++) { position += totalheight[i] } this._listView.scrollTo({y: position}); this.refs.toast.show(letter, DURATION.LENGTH_SHORT); } _renderRightLetters(letter, index) { return ( <TouchableOpacity key={'letter_idx_' + index} activeOpacity={0.6} onPress={() => { this._scrollTo(index, letter) }}> <View style={styles.letter}> <Text style={styles.letterText}>{letter}</Text> </View> </TouchableOpacity> ); } _renderListBox(cityJson, rowId) { return ( <TouchableOpacity key={'list_item_' + cityJson.id} style={styles.rowViewBox} onPress={() => { that._cityNameClick(cityJson) }}> <View style={styles.rowdataBox}> <Text style={styles.rowDataTextBox}>{cityJson.name}</Text> </View> </TouchableOpacity> ); } _renderListRow(cityJson, rowId) { console.log('rowId===>' + rowId + ", cityJson====>" + JSON.stringify(cityJson)); if (rowId === key_now || rowId === key_hot || rowId === key_last_visit) { return that._renderListBox(cityJson, rowId); } return ( <TouchableOpacity key={'list_item_' + cityJson.id} style={styles.rowView} onPress={() => { that._cityNameClick(cityJson) }}> <View style={styles.rowdata}> <Text style={styles.rowdatatext}>{cityJson.name}</Text> </View> </TouchableOpacity> ) } _renderListSectionHeader(sectionData, sectionID) { return ( <View style={styles.sectionView}> <Text style={styles.sectionText}> {sectionData} </Text> </View> ); } render() { return ( <View style={styles.container}> <View style={styles.listContainner}> <ListView ref={listView => this._listView = listView} contentContainerStyle={styles.contentContainer} dataSource={this.state.dataSource} renderRow={this._renderListRow} renderSectionHeader={this._renderListSectionHeader} enableEmptySections={true} initialListSize={500}/> <View style={styles.letters}> {this.state.letters.map((letter, index) => this._renderRightLetters(letter, index))} </View> </View> <Toast ref="toast" position='top' positionValue={200} fadeInDuration={750} fadeOutDuration={1000} opacity={0.8}/> </View> ) } } const styles = StyleSheet.create({ container: { // paddingTop: 50, flex: 1, flexDirection: 'column', backgroundColor: '#F4F4F4', }, listContainner: { height: Dimensions.get('window').height, marginBottom: 10 }, contentContainer: { flexDirection: 'row', width: width, backgroundColor: 'white', justifyContent: 'flex-start', flexWrap: 'wrap' }, letters: { position: 'absolute', height: height, top: 0, bottom: 0, right: 10, backgroundColor: 'transparent', // justifyContent: 'flex-start', // alignItems: 'flex-start' alignItems: 'center', justifyContent: 'center' }, letter: { height: height * 4 / 100, width: width * 4 / 50, justifyContent: 'center', alignItems: 'center' }, letterText: { textAlign: 'center', fontSize: height * 1.1 / 50, color: '#e75404' }, sectionView: { paddingTop: 5, paddingBottom: 5, height: 30, paddingLeft: 10, width: width, backgroundColor: '#F4F4F4' }, sectionText: { color: '#e75404', fontWeight: 'bold' }, rowView: { height: ROWHEIGHT, paddingLeft: 10, paddingRight: 10, borderBottomColor: '#F4F4F4', borderBottomWidth: 0.5 }, rowdata: { paddingTop: 10, paddingBottom: 2 }, rowdatatext: { color: 'gray', width: width }, rowViewBox: { height: ROWHEIGHT_BOX, width: (width - 30) / 3, flexDirection: 'row', backgroundColor: '#ffffff' }, rowdataBox: { borderWidth: 1, borderColor: '#DBDBDB', marginTop: 5, marginBottom: 5, paddingBottom: 2, marginLeft: 10, marginRight: 10, flex: 1, justifyContent: 'center', alignItems: 'center' }, rowDataTextBox: { marginTop: 5, flex: 1, height: 20 } });
然后在头部还需要实现一个搜索框。
SearchBox.js
'use strict'; import React, {Component} from 'react'; import { View, TextInput, StyleSheet, Platform, } from 'react-native'; export default class SearchBox extends Component { constructor(props) { super(props); this.state = { value: '' }; } onEndEditingKeyword(vv) { console.log(vv); } onChanegeTextKeyword(vv) { console.log('onChanegeTextKeyword', vv); this.setState({value: vv}); this.props.onChanegeTextKeyword(vv); } render() { return ( <View style={styles.container}> <View style={styles.inputBox}> <View style={styles.inputIcon}> </View> <TextInput ref="keyword" autoCapitalize="none" value={this.props.keyword} onChangeText={this.onChanegeTextKeyword.bind(this)} returnKeyType="search" maxLength={20} style={styles.inputText} underlineColorAndroid="transparent" placeholder={'输入城市名或拼音查询'}/> </View> </View> ) } } const styles = StyleSheet.create({ container: { marginTop: 5, marginBottom: 5, backgroundColor: '#ffffff', flexDirection: 'row', height: Platform.OS === 'ios' ? 35 : 45, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#cdcdcd', paddingBottom: 5 }, inputBox: { height: Platform.OS === 'ios' ? 30 : 40, marginLeft: 5, marginRight: 5, flex: 1, flexDirection: 'row', backgroundColor: '#E6E7E8' }, inputIcon: { margin: Platform.OS === 'ios' ? 5 : 10 }, inputText: { alignSelf: 'flex-end', marginTop: Platform.OS === 'ios' ? 0 : 0, flex: 1, height: Platform.OS === 'ios' ? 30 : 40, marginLeft: 2, marginRight: 5, fontSize: 12, lineHeight: 30, textAlignVertical: 'bottom', textDecorationLine: 'none' } });
最终效果:
最后是界面的绘制,这里就不多说了,大家可以下载源码自行查看。源码地址:react-native-city_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。