1. 程式人生 > 實用技巧 >React Native 實現城市選擇元件

React Native 實現城市選擇元件

前言

很多 App 都有城市選擇的功能,今天帶大家編寫一個城市選擇元件。下面是這個元件的效果圖。

功能分析

從上圖中可以看出,我們將所有城市按照字母區分塊,右邊是字母索引。通過點選右邊的字母可以跳轉到相對應的塊,同樣的,在移動左邊的列表的時候,右邊也會跟隨移動來顯示不同的高亮。

這個元件中,我們通過reactNative 提供的 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 {
    const
ructor(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。如果還需要其它功能,大家自己擴充套件即可。