城市列表选择是很多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
来源: http://www.jb51.net/article/120592.htm