目錄
前言
很多 App 都有城市選擇的功能,今天帶大家編寫一個城市選擇元件。下面是這個元件的效果圖。
功能分析
從上圖中可以看出,我們將所有城市按照字母區分塊,右邊是字母索引。透過點選右邊的字母可以跳轉到相對應的塊,同樣的,在移動左邊的列表的時候,右邊也會跟隨移動來顯示不同的高亮。
這個元件中,我們透過 React Native 提供的 FlatList 來實現。最開始我的實現是透過自己計算高度,在閱讀文件的時候,發現 FlatList 元件提供了幾個很好用的特性:
onViewableItemsChanged
: 在可見行元素變化時呼叫。可見範圍和變化頻率等引數的配置請設定viewabilityConfig屬性。scrollToIndex
: 將位於指定位置的元素滾動到可視區的指定位置,當viewPosition 為 0 時將它滾動到螢幕頂部,為 1 時將它滾動到螢幕底部,為 0.5 時將它滾動到螢幕中央。
思路
城市選擇元件最重要是需要城市資料的來源,可以透過網路獲取,但是由於資料量過大,網路的效能不太理想。我提前準備了一個 json 檔案放到專案中。資料的結構如下:
{
"data": [
{
"key": "A",
"cities": [
{
"key": "152900",
"city": "阿拉善盟"
},
...
]
},
{
"key": "B",
"cities": [
{
"key": "130600",
"city": "保定市"
},
...
]
},
...
]
}
在渲染的時候,透過 FlatList 將每個字母渲染出來,每個字母中的城市透過遍歷來渲染出來。在每次可見專案變化時,右邊的字母列表透過判斷是否等於當前可見專案來判斷高亮狀態。點選右邊的字母時,則跳轉到指定的 index 上。
實現
先準備好城市資料放到 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。如果還需要其它功能,大家自己擴充套件即可。
本作品採用《CC 協議》,轉載必須註明作者和本文連結