前言
很多 App 都有城市選擇的功能,今天帶大家編寫一個城市選擇組件。下面是這個組件的效果圖。
功能分析
從上圖中可以看出,我們將所有城市按照字母區分塊,右邊是字母索引。通過點擊右邊的字母可以跳轉到相對應的塊,同樣的,在移動左邊的列表的時候,右邊也會跟隨移動來顯示不同的高亮。
這個組件中,我們通過 react Native 提供的 FlatList 來實現。最開始我的實現是通過自己計算高度,在閱讀文檔的時候,發現 FlatList 組件提供了幾個很好用的特性:
onViewableItemsChanged scrollToIndex
思路
城市選擇組件最重要是需要城市數據的來源,可以通過網絡獲取,但是由於數據量過大,網絡的性能不太理想。我提前准備了一個 json 文件放到項目中。數據的結構如下:
{
"data": [ { "key": "A", "cities": [ { "key": "152900", "city": "阿拉善盟" }, ... ] }, { "key": "B", "cities": [ { "key": "130600", "city": "保定市" }, ... ] }, ... ] }
在渲染的時候,通過 FlatList 將每個字母渲染出來,每個字母中的城市通過遍歷來渲染出來。在每次可見項目變化時,右邊的字母列表通過判斷是否等於當前可見項目來判斷高亮狀態。點擊右邊的字母時,則跳轉到指定的 index 上。
廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com
實現
先准備好城市數據放到 src/assets 目錄中:
接下來在 src 目錄中新建一個 ChoseCity.js 文件作為組件。在組件中現將文件內容導入進來,用作 FlatList 組件的數據。
import react, { Component } from 'react'; import { FlatList, View, StyleSheet } from 'react-native'; export default class ChoseCity extends Component { constructor(props) { super(props); this.state = { data: [], // 用於存放所有的城市數據 right: [], // 右邊的字母導航數據 currentLetter: 'A' // 當前選中的城市 } } async componentDidMount() { const { data } = await require('./assets/cities.json'); console.log(data); } render() { return ( <View> </View> ); } }
在調試信息中我們可以看到 data 的數據結構:
接下來我們將 data 中的數據存放到 state 中:
async componentDidMount() { const { data } = await require('./assets/cities.json'); let cityInfo = []; let right = []; // 這里的保證了城市數據和右邊的字母導航同步 data.map((item, index) => { cityInfo[index] = { key: item.key, data: item.cities }; right[index] = item.key; }); this.setState({ data: cityInfo, right: right }); }
接下來我們就開始渲染 FlatList 中的數據:
renderItem = ({ item, index }) => ( <View style={styles.cityPiece}> <Text style={styles.keyText}>{item.key}</Text> <View style={styles.cities}> {item.data.map(({ city }, index) => ( <TouchableOpacity key={index} style={styles.cityItem}> <Text>{city}</Text> </TouchableOpacity> ))} </View> </View> ); render() { return ( <View> <FlatList data={this.state.data} renderItem={this.renderItem} keyExtractor={item => item.key} /> </View> ); }
上面代碼中,我使用了 map 來遍歷每個字母中包含的城市。效果如下:
接下來我們就來實現右邊的導航,在 render 方法的根 View 組件中添加下面代碼:
<View style={styles.right}> {/* 由於數據不多,也直接使用 map 來遍歷 */} {this.state.right.map((item, index) => ( <TouchableOpacity key={index}> <Text style={[this.state.currentLetter === item && { color: '#FD7700' }]}>{item}</Text> </TouchableOpacity> ))} </View> }
到這里,外觀部分已經全部實現了,這里是樣式代碼:
const styles = StyleSheet.create({ keyText: { fontSize: 16, fontWeight: 'bold', }, cityPiece: { marginTop: 6, backgroundColor: '#FFF', paddingLeft: 21, paddingRight: 21, paddingTop: 15, paddingBottom: 15 }, cities: { flexWrap: 'wrap', flexDirection: 'row', }, cityItem: { flex: 0, backgroundColor: '#F6F5F5', paddingLeft: 22, paddingRight: 22, paddingTop: 11, paddingBottom: 11, borderRadius: 18, marginTop: 14, marginRight: 10, }, right: { position: 'absolute', top: 0, right: 0, bottom: 0, paddingRight: 5, paddingTop: 5, paddingBottom: 5, justifyContent: 'space-between', backgroundColor: '#F6F5F5', paddingLeft: 10, }, })
接下來我們先實現右邊導航跟隨數據的滾動來改變高亮。這里需要用到 onViewableItemsChanged 。這里回調函數給了我們兩個參數: viewableItems 和 changed ,我們來看看它們的結構。
從這里可以看出,viewableItems 數組中第一個元素是當前可見的第一項,所以我們只需要第一個可見元素作為當前項即可。
onViewableItemsChanged = ({ viewableItems, changed }) => { // 將第一個可見的元素,作為當前元素 this.setState({ currentLetter: viewableItems[0].key }); } render() { return ( <View> <FlatList data={this.state.data} showsVerticalScrollIndicator={false} renderItem={this.renderItem} keyExtractor={item => item.key} onViewableItemsChanged={this.handleViewableItemsChanged} /> </View> ); }
到這里已經實現了跟隨高亮,怎么樣很簡單吧,是不是比想象中更簡單,接下來我們來實現點擊字母跳轉到指定字母塊,這里要用到 scrollToIndex ,代碼如下:
scrollTo = (index) => { this.list.scrollToIndex({ viewOffset: -6, viewPosition: 0, index, animated: true }); } render() { return ( <View style={{backgroundColor: '#F6F5F5'}}> <View style={styles.right}> {/* 由於數據不多,也直接使用 map 來遍歷 */} {this.state.right.map((item, index) => ( {/* 利用 index 來進行跳轉 */} <TouchableOpacity key={index} onPress={() => {this.scrollTo(index)}}> <Text style={[this.state.currentLetter === item && { color: '#FD7700' }]}>{item}</Text> </TouchableOpacity> ))} </View> <FlatList style={{marginRight: 30}} data={this.state.data} ref={flatList => this.list = flatList} showsVerticalScrollIndicator={false} renderItem={this.renderItem} keyExtractor={item => item.key} onViewableItemsChanged={this.handleViewableItemsChanged} /> </View> ); }
總結
這個組件並不是一個完善的組件,還有一個小 bug,當滾動到 z 的時候,右邊 z 並不會高亮,這里可以判斷是否已經到底,如果到底,則高亮字母 z。如果還需要其它功能,大家自己擴展即可。