React|React Native城市选择与切换

虽然React Native已经不在是当前热门的跨平台技术了,但是还是可以看到很多的公司和个人在用,Flutter有Flutter好,RN呢也有适合自己的地方,至于究竟哪个更好,我们不做过多的比较。
最近,我在升级之前的《React Native移动开发实战》一书,书中的项目有城市切换的功能,效果如下图所示。
React|React Native城市选择与切换
文章图片

可以看到,这个城市选择页面是很常规的,包含了当前定位城市和城市列表,右侧可以通过SlideBar进行快捷定位,除此之外,此组件还支持搜索功能。
首先,我们看一下城市列表,对于这一功能,我们可以使用SectionList组件,因为我们可以使用SectionList的ListHeaderComponent属性来实现当前定位布局,而右边的字母索引效果需要借助SectionList的scrollToLocation()函数,如下所示。

const _scrollTo = (index, letter) => { listViewRef?.current?.scrollToLocation({itemIndex: 0, sectionIndex: index}); };

而搜索功能就更加简单了,使用FlatList组件展示即可,此处也可以使用list.map循环来开发列表功能。
CitySelectScreen.js
import React, {useState, useEffect} from 'react'; import { View, Text, TextInput, StyleSheet, TouchableOpacity, Keyboard, } from 'react-native'; import PropTypes from 'prop-types'; import {CityList} from './components'; import apiRequest from '../../api'; import Header from '../../common/Header/Header'; const CitySelectScreen = ({location = '上海市', navigation}) => { let inputRef = null; const [cities, setCities] = useState([]); const [currentCityList, setCurrentCityList] = useState({}); const [isFocused, setIsFocused] = useState(false); const [result, setResult] = useState([]); const [keyword, setKeyword] = useState(''); useEffect(() => { getCities(); }, []); const onChangeText = e => { setKeyword(e); }; const onSelectCity = city => { setTimeout(() => { navigation.navigate('SelectCinemaScreen', { title: city.CITY_NAME, CITY_CD: city.CITY_CD, }); }, 200); setResult([]); }; const searchSubmit = () => { if (isFocused) { inputRef.blur(); setIsFocused(false); setResult([]); Keyboard.dismiss(); } else { setIsFocused(true); inputRef.focus(); } }; const getCities = async () => { let url = 'https://prd-api.cgv.com.cn/product/areas/that/group'; const res = await apiRequest.get(url); setCities(res); }; const searchCities = async () => { let url = 'https://prd-api.cgv.com.cn/product/areas/that/group'; const params = {condition: keyword}; const res = await apiRequest.get(url, params); console.log(res[0].data); setResult(res[0].data); }; const onCurrentPress = (name = '上海市') => { cities.map(item => item.data.map(val => { if (val.CITY_NAME === name) { onSelectCity(val); return null; } }), ); }; const renderSearchView = () => { return ( { inputRef = c; }} onChangeText={onChangeText} returnKeyType="search" onSubmitEditing={() => { if (keyword) { searchCities(); } }} onFocus={() => setIsFocused(true)} placeholder="输入城市名或拼音" /> searchSubmit(isFocused)}> {!isFocused ? '搜索' : '取消'} ); }; return ( {renderSearchView()} {(!isFocused && !keyword && keyword.length < 1) || !isFocused ? ( ) : ()} ); }; const SearchResult = ({list, onSelectCity}) => { return ( {list.map((item, index) => ( { onSelectCity(item); }}> {item.CITY_NAME} ))} ); }; const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', }, searchView: { height: 48, flexDirection: 'row', backgroundColor: '#fff', alignItems: 'center', paddingLeft: 10, paddingRight: 10, }, searchTxt: { color: '#FC5869', marginRight: 5, fontSize: 16, }, rowView: { backgroundColor: '#fff', height: 44, paddingLeft: 13, justifyContent: 'center', }, leftIcon: { width: 28, height: 28, paddingLeft: 13, }, }); CitySelectScreen.propTypes = { cities: PropTypes.array, getCities: PropTypes.func, }; export default CitySelectScreen;

【React|React Native城市选择与切换】CityList.js代码如下:
import React, {useEffect, useRef} from 'react'; import { View, SectionList, TouchableOpacity, StyleSheet, Text, Image, Dimensions, } from 'react-native'; import PropTypes from 'prop-types'; import ItemSeparatorComponent from '../../../../common/ItemSeparator'; import location from '../../../../assets/images/home/location.png'; import refresh from '../../../../assets/images/home/refresh.png'; const {width} = Dimensions.get('window'); const propTypes = { keyword: PropTypes.string, onChangeTextKeyword: PropTypes.func, }; const defaultProps = {}; const CityList = ({ onSelectCity, allCityList = [], currentCity, onCurrentCityPress, position: _position, }) => { const listViewRef = useRef(null); useEffect(() => { console.log(allCityList); }, []); const city = currentCity && currentCity.city ? currentCity.city : '定位失败,请手动选择城市'; const _cityNameClick = cityJson => { onSelectCity(cityJson); }; const getLocation = async () => { // await PermissionsAndroid.request( //PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, // ); // const res = await Geocode.reverse({ //latitude: '29.604451007313266', //longitude: '106.52727499999997', // }); // _position(res); }; const CityHeader = props => { const {currentCity = '上海', onCurrentCityPress} = props; return ( 当前城市 onCurrentCityPress(currentCity)} style={styles.headerContainer}> {currentCity} getLocation()}> ); }; const _renderListRow = (cityJson, rowId) => { return ( _cityNameClick(cityJson.item)}> {cityJson.item.CITY_NAME} ); }; const _scrollTo = (index, letter) => { listViewRef?.current?.scrollToLocation({itemIndex: 0, sectionIndex: index}); }; const _renderRightLetters = (letter, index) => { return ( { _scrollTo(index, letter); }}> {letter} ); }; return ( ({ length: 44, offset: 44 * index, index, })} ListHeaderComponent={ } ref={listViewRef} sections={allCityList} keyExtractor={(item, index) => index.toString()} renderItem={_renderListRow} ItemSeparatorComponent={() => } renderSectionHeader={({section: {name}}) => ( {name} )} stickySectionHeadersEnabled={true} /> {allCityList.map((item, index) => _renderRightLetters(item.name, index), )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F4F4F4', }, sectionTitle: { paddingVertical: 5, paddingLeft: 12, backgroundColor: '#F3F4F5', }, iconContainer: { height: 40, justifyContent: 'center', alignItems: 'center', }, leftIconContainer: { marginEnd: 12, }, rightIconContainer: { marginStart: 8, }, headerView: { width: width, display: 'flex', flexDirection: 'row', position: 'relative', alignItems: 'center', backgroundColor: '#fff', }, leftIcon: { width: 15, height: 13, marginLeft: 15, marginRight: 5, }, rowView: { paddingLeft: 12, backgroundColor: '#fff', }, rowData: { width: width, height: 44, justifyContent: 'center', }, headerContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 12, height: 44, }, headerLeft: { flexDirection: 'row', alignItems: 'center', }, letter: { marginBottom: 3, }, letterSpace: { position: 'absolute', right: 4, bottom: 0, top: 0, justifyContent: 'center', }, }); CityList.propTypes = propTypes; CityList.defaultProps = defaultProps; export default CityList;

另外,我们的网络请求使用的是Axios,相关内容可以查看我之前文章的介绍:React Native使用axios进行网络请求
源码:https://github.com/xiangzhihong/rn_city

    推荐阅读