Skip to content

React Native 实现城市选择组件 #6

@collinxz-coder

Description

@collinxz-coder

目录

前言

很多 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。这里回调函数给了我们两个参数:viewableItemschanged,我们来看看它们的结构。

从这里可以看出,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。如果还需要其它功能,大家自己扩展即可。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      pFad - Phonifier reborn

      Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

      Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


      Alternative Proxies:

      Alternative Proxy

      pFad Proxy

      pFad v3 Proxy

      pFad v4 Proxy