虽然React Native已经不在是当前热门的跨平台技术了,但是还是可以看到很多的公司和个人在用,Flutter有Flutter好,RN呢也有适合自己的地方,至于究竟哪个更好,我们不做过多的比较。
最近,我在升级之前的《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 (
<View style={styles.searchView}>
<TextInput
style={{flex: 1}}
assignRef={c => {
inputRef = c;
}}
onChangeText={onChangeText}
returnKeyType="search"
onSubmitEditing={() => {
if (keyword) {
searchCities();
}
}}
onFocus={() => setIsFocused(true)}
placeholder="输入城市名或拼音"
/>
<TouchableOpacity onPress={() => searchSubmit(isFocused)}>
<Text style={styles.searchTxt}>{!isFocused ? '搜索' : '取消'}</Text>
</TouchableOpacity>
</View>
);
};
return (
<View style={styles.container}>
<Header title={'选择城市'} />
{renderSearchView()}
<View style={{flex: 1}}>
{(!isFocused && !keyword && keyword.length < 1) || !isFocused ? (
<CityList
onCurrentCityPress={onCurrentPress}
onSelectCity={onSelectCity}
currentCity={location}
allCityList={cities}
currentCityList={currentCityList}
/>
) : (
<SearchResult list={result} onSelectCity={onSelectCity} />
)}
</View>
</View>
);
};
const SearchResult = ({list, onSelectCity}) => {
return (
<View style={{marginTop: 10}}>
{list.map((item, index) => (
<TouchableOpacity
activeOpacity={0.7}
key={index.toString()}
style={styles.rowView}
onPress={() => {
onSelectCity(item);
}}>
<View style={styles.rowdata}>
<Text type="subheading">{item.CITY_NAME}</Text>
</View>
</TouchableOpacity>
))}
</View>
);
};
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;
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 (
<View>
<View style={styles.sectionTitle}>
<Text style={{fontSize: 15}}>当前城市</Text>
</View>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => onCurrentCityPress(currentCity)}
style={styles.headerContainer}>
<View style={styles.headerLeft}>
<Image source={location} style={{width: 20, height: 20}} />
<Text type="subheading" style={{marginLeft: 2}}>
{currentCity}
</Text>
</View>
<TouchableOpacity activeOpacity={0.7} onPress={() => getLocation()}>
<Image source={refresh} style={{width: 20, height: 20}} />
</TouchableOpacity>
</TouchableOpacity>
</View>
);
};
const _renderListRow = (cityJson, rowId) => {
return (
<TouchableOpacity
activeOpacity={0.7}
key={`list_item_${cityJson.item.CITI_CD}`}
style={styles.rowView}
onPress={() => _cityNameClick(cityJson.item)}>
<View style={styles.rowData}>
<Text type="subheading">{cityJson.item.CITY_NAME}</Text>
</View>
</TouchableOpacity>
);
};
const _scrollTo = (index, letter) => {
listViewRef?.current?.scrollToLocation({itemIndex: 0, sectionIndex: index});
};
const _renderRightLetters = (letter, index) => {
return (
<TouchableOpacity
key={`letter_idx_${index}`}
activeOpacity={0.6}
onPress={() => {
_scrollTo(index, letter);
}}>
<View style={styles.letter}>
<Text>{letter}</Text>
</View>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<SectionList
getItemLayout={(param, index) => ({
length: 44,
offset: 44 * index,
index,
})}
ListHeaderComponent={
<CityHeader
currentCity={city}
onCurrentCityPress={onCurrentCityPress}
/>
}
ref={listViewRef}
sections={allCityList}
keyExtractor={(item, index) => index.toString()}
renderItem={_renderListRow}
ItemSeparatorComponent={() => <ItemSeparatorComponent />}
renderSectionHeader={({section: {name}}) => (
<View style={styles.sectionTitle}>
<Text style={{fontSize: 15}}>{name}</Text>
</View>
)}
stickySectionHeadersEnabled={true}
/>
<View style={styles.letterSpace}>
{allCityList.map((item, index) =>
_renderRightLetters(item.name, index),
)}
</View>
</View>
);
};
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进行网络请求
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。