欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ReactNative 滑动字母选择城市 以及搜索功能

程序员文章站 2022-05-31 08:33:49
...

先上效果图

ReactNative 滑动字母选择城市 以及搜索功能

遇到的问题

  • 右侧字母选择器 高度问题,
  • 右侧字母选择器 如何使用手势检测panresponse
  • 右侧字母选择器 计算高度如何判断是触摸到那个字母上的(思考 如果是==native应用是如何做的==…刚看过 native应用的城市列表也是通过计算每个字母的高度来检测的)动态创建的控件

右边滑动的原理: 通过onlayout计算每个字母高度

,然后加入数组 , 手指触摸字母列表时 知道 触摸的y坐标

这样再减去列表距离顶部的距离 就是字母列表的初始坐标.根据

y坐标和刚才通过onlayout计算出的数组进行比对在那个区间则是

哪一个字母的item

ReactNative 滑动字母选择城市 以及搜索功能

下面上代码

/**
 * Created by liuml on 2017/10/5.
 */

import React, {Component} from 'react';
import {
    View,
    Image,
    TouchableOpacity,
    Modal,
    Text,
    StatusBar,
    ListView,
    Platform,
    Dimensions,
    StyleSheet,
    RefreshControl,
    Alert,
    TextInput,
    PanResponder
} from 'react-native';
import _ from 'lodash';
import NavigationBar from './compont/NavigationBar'

const {width, height} = Dimensions.get('window')
const SECTIONHEIGHT = 30, ROWHEIGHT = 40

//这是利用lodash的range和数组的map画出26个英文字母


var Util = require('./util/util');//工具类
var ScreenUtil = require('./util/ScreenUtil');//工具类
let city = [];//城市的数组 里面放的是对象
var totalheight = [];//每个字母对应的城市和字母的总高度  比如所有a字母中数据的高度
var lettersItemheight = [];//每个字母的y坐标
var myLetters = [];//我的字母数组
var myDataBlob = {};//获取到的数据
var lettersBottom = 10;//字母列表距离底部高度
var mySectionIDs = []; //组id
var myRowIDs = [];//组内
var cityData = [];//获取到的数据
var totalNumber = 10;//总条数的数据
var searchHeight = 35;//搜索框高度
var searchHeightMargin = 2;//搜索框margin
var lettersHeight;//字母列表高度
var that ;
export default class CityList extends Component {


    constructor(props) {
        super(props);
        // 获取组中数据
        var getSectionData = (myDataBlob, mySectionIDs) => {
            // console.log("组id mySectionIDs = " + mySectionIDs);
            // console.log(`组中数据 = ${myDataBlob[mySectionIDs]}`);
            return myDataBlob[mySectionIDs];
        };
        // 获取行中的数据
        var getRowData = (myDataBlob, mySectionIDs, myRowIDs) => {
            // console.log(`行中数据 = ${myDataBlob[myRowIDs]}`);
            return myDataBlob[myRowIDs];
        };
        this.state = {
            dataSource: new ListView.DataSource({
                getSectionHeaderData: getSectionData,
                getRowData: getRowData,
                rowHasChanged: (row1, row2) => row1 !== row2,
                sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
            }),
            isLoading: true,
            lettersShow: true
        }
        that = this;
    }

    //加载数据
    loadData = () => {

        // var thiz = this;
        Util.post('http://ovji4jgcd.bkt.clouddn.com/Mycity.json', {},
            (ret) => {
                // console.log(ret);
                if (ret.resCode == 1 && ret.data.length > 0) {

                    cityData = ret.data;
                    // console.log(cityData);
                    totalNumber = ret.totalNumber;
                    //一系列的操作 遍历数组
                    console.log("正确的总数据: " + cityData);
                    this.setData(cityData);
                }
            });


    }

    //设置数据
    setData = (cityData) => {
        for (let i = 0; i < cityData.length; i++) {
            var mysectionName = 'Section_' + i;
            let cityMode = cityData[i].data;
            let zimu = cityData[i].zimu;
            mySectionIDs.push(mysectionName)
            myRowIDs[i] = [];
            var innerLoop = cityData[i].data; //内循环中的城市
            myDataBlob[mysectionName] = zimu;//把字母放入总数据
            myLetters.push(zimu)//把字母放入用于右边的字母列表
            for (let jj = 0; jj < innerLoop.length; jj++) {
                let rowName = i + '_' + jj;
                myRowIDs[i].push(rowName);
                myDataBlob[rowName] = innerLoop[jj];
            }
            //组的高度  +  上行的高度 * 有多少行
            var eachheight = SECTIONHEIGHT + ROWHEIGHT * cityMode.length
            totalheight.push(eachheight)
        }
        let size = myLetters.length;
        // console.log("字母数量" + size);
        // console.log("lettersHeight = " + lettersHeight);
        //关闭对话框 设置数据源
        // console.log("打印setstate===================")
        this.setState({
            dataSource: this.state.dataSource.cloneWithRowsAndSections(myDataBlob, mySectionIDs, myRowIDs),
            isLoading: false,
            lettersShow: true
        })
    }
    //更新数据
    updateData = (cityData) => {
        console.log("更新的数据: " + cityData);
        let myDataBlob = [], mySectionIDs = [], myRowIDs = [];
        for (let i = 0; i < cityData.length; i++) {
            let mysectionName = 'Section_' + i;
            // let cityMode = cityData[i].data;
            let zimu = cityData[i].zimu;
            mySectionIDs.push(mysectionName)
            myRowIDs[i] = [];
            let innerLoop = cityData[i].data; //内循环中的城市
            myDataBlob[mysectionName] = zimu;//把字母放入总数据
            // myLetters.push(zimu)//把字母放入用于右边的字母列表

            for (let jj = 0; jj < innerLoop.length; jj++) {
                let rowName = i + '_' + jj;
                myRowIDs[i].push(rowName);
                myDataBlob[rowName] = innerLoop[jj];
            }
        }

        // console.log("打印setstate===================")
        this.setState({
            dataSource: this.state.dataSource.cloneWithRowsAndSections(myDataBlob, mySectionIDs, myRowIDs),
            isLoading: false,
            lettersShow: false
        })
    }

    //返回箭头
    handleBack = () => {
        //把任务栈顶部的任务清除
        this.props.navigation.goBack();
    }


    //左边的箭头
    getNavLeftBtn = () => {
        return <View style={{flexDirection: 'row', alignItems: 'center'}}>
            <TouchableOpacity
                activeOpacity={0.7}
                onPress={this.handleBack}>
                <Image source={require('../res/image/ic_arrow_back_white_36pt.png')}
                       style={{width: 24, height: 24}}/>
            </TouchableOpacity>
        </View>;
    }


    //页面渲染加载完调用加载数据
    componentDidMount() {
        this.loadData();
    }

    //设置行
    renderRow(rowData, rowId) {
        return (
            <TouchableOpacity
                key={rowId}
                style={{height: ROWHEIGHT, justifyContent: 'center', paddingLeft: 20, paddingRight: 30}}
                onPress={() => {
                    that.changedata(rowData)
                }}>
                <View style={styles.rowdata}><Text style={styles.rowdatatext}>{rowData}</Text></View>
            </TouchableOpacity>
        )
    }

    //设置组
    renderSectionHeader = (sectionData, sectionID) => {
        return (
            <View style={{height: SECTIONHEIGHT, justifyContent: 'center', paddingLeft: 5, backgroundColor: 'gray'}}>
                <Text style={{color: 'black', fontWeight: 'bold', marginLeft: 10}}>
                    {sectionData}
                    {/*{console.log(`sectionData = ${sectionData}`)}*/}
                </Text>
            </View>
        )
    }
    // render ringht index Letters 右边的字母
    // onLayout 测量字母
    renderLetters(letter, index) {
        return (
            <TouchableOpacity
                onLayout={({nativeEvent: e}) => this.oneLetterLayout(e)}
                key={index} activeOpacity={0.7}
                onPressIn={() => {
                    this.scrollTo(index)
                }}>
                <View
                    style={styles.letter}>
                    <Text style={styles.letterText}>{letter}</Text>
                </View>
            </TouchableOpacity>
        )
    }


    //回调改变显示的城市
    changedata = (cityname) => {
        const {navigation} = this.props;
        const {state, goBack} = navigation;
        console.log(state);
        console.log(cityname);
        state.params.callback(cityname)
        goBack();
    }

    //touch right indexLetters, scroll the left
    scrollTo = (index) => {
        let position = 0;

        for (let i = 0; i < index; i++) {
            position += totalheight[i]
        }
        this._listView.scrollTo({
            y: position, animated: true
        })
    }


    //搜索框高度
    searchLayout = (e) => {
        // console.log('searchLayout 高度' + e.layout.height);
    }
    //字母高度
    lettersLayout = (e) => {
        // console.log('lettersLayout 高度' + e.layout.height);
        // console.log('lettersLayout y坐标' + e.layout.y);
        lettersHeight = height - searchHeight * 2 - searchHeightMargin * 2;
        // console.log('字母列表高度 = ' + lettersHeight);
        // console.log('height = ' + height);
    }
    //每个字母高度
    oneLetterLayout = (e) => {
        // console.log('lettersLayout 高度' + e.layout.height);
        // console.log('每个字母高度 y坐标' + e.layout.y);
        // if (lettersItemheight.length >= 0) {
        //     lettersItemheight = [];
        // }
        if (lettersItemheight.length != myLetters.length) {
            lettersItemheight.push(e.layout.y);
        }
    }
    //文本改变
    changeText = (text) => {
        // console.log("改变的文本: " + text);
        text = text.trim();
        if (text != "") {
            if (/^[\u4e00-\u9fa5]/.test(text)) {//是否有中文
                console.log("===有中文===")
                let mCityData = [];
                console.log("原始数据: " + cityData);
                let k = 0;
                for (let i = 0; i < cityData.length; i++) {
                    let data = [];
                    data = cityData[i].data;
                    // console.log("data = " + data);
                    // console.log("data 长度 = " + data.length);
                    let isHas = false;//是否存在

                    var itemData = [];//这里是匹配的城市数据
                    for (let j = 0; j < data.length; j++) {
                        // console.log("字符串是否相等"+data[j].includes(text));
                        // console.log("输入框内: "+text);
                        // console.log("数据内: "+data[j]);

                        // itemData = cityData[i];
                        if (data[j].includes(text)) {
                            console.log("正确的itm data = " + cityData[i].data);
                            itemData.push(data[j]);
                            // mCityData.push(data);
                            isHas = true;//
                        }
                    }
                    //内层循环结束
                    console.log("itemData = " + itemData);
                    if (isHas) {
                        let obj;
                        obj = {'zimu': cityData[i].zimu, 'data': itemData, 'id': cityData[i].id};
                        console.log("添加的数据 " + obj.zimu + " " + obj.data);
                        console.log("添加的数据 " + obj);
                        mCityData[k] = obj;
                        k++;
                    }



                }
                // console.log("过滤后的数据: " + mCityData[0].data);
                // console.log("过滤后的数据: " + mCityData[1].data);
                // console.log("过滤后的数据长度: " + mCityData.length);
                // console.log("过滤后的数据: "+mCityData );
                // mCityData.map((item, i) => {
                //     console.log("\n" + item.data);
                // })
                this.updateData(mCityData);

            } else {//否则是英文
                for (let i = 0; i < cityData.length; i++) {
                    console.log("===英文===")
                    // console.log("打印字母 " + cityData[i].zimu);
                    // console.log("打印改变的文字 " + text.toLowerCase());
                    if (cityData[i].zimu == text.toUpperCase()) {
                        // if (cityData[i].zimu.indexOf(text) != -1) {
                        let mCityData = [];
                        mCityData.push(cityData[i]);
                        this.updateData(mCityData);
                        return;
                    }
                }
            }
        } else {
            // myDataBlob.map((item ,i)=>{
            //     console.log(item);
            // })
            // console.log(myDataBlob);
            // console.log(mySectionIDs);
            // console.log(myRowIDs);
            //
            // console.log("打印setstate===================")
            console.log("===数据为空刷新===")
            this.setState({
                dataSource: this.state.dataSource.cloneWithRowsAndSections(myDataBlob, mySectionIDs, myRowIDs),
                isLoading: false,
                lettersShow: true
            })
        }

    }

    componentWillMount() {
        this._panGesture = PanResponder.create({
            //要求成为响应者:
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
            onPanResponderTerminationRequest: (evt, gestureState) => true,

            onPanResponderGrant: (evt, gestureState) => {

                // console.log('触摸 当响应器产生时的屏幕坐标 \n x:' + gestureState.x0 + ',y:' + gestureState.y0);
                let value = gestureState.y0 - searchHeight * 2 - lettersBottom + 1;
                // console.log("点击的点 : " + value);

                for (let i = 0; i < lettersItemheight.length; i++) {
                    if (value < 0) {
                        this.scrollTo(0);
                    } else if (value > lettersItemheight[i]) {
                        this.scrollTo(i);
                    }
                }
            },
            onPanResponderMove: (evt, gestureState) => {
                // console.log('移动 最近一次移动时的屏幕坐标\n moveX:' + gestureState.moveX + ',moveY:' + gestureState.moveY);
                // console.log('移动 当响应器产生时的屏幕坐标\n x0:' + gestureState.x0 + ',y0:' + gestureState.y0);
                // console.log('移动 从触摸操作开始时的累计纵向路程\n dx:' + gestureState.dx + ',dy :' + gestureState.dy);

                let value = gestureState.moveY - searchHeight * 2 - lettersBottom + 1;
                // console.log("移动的点 " + value);

                for (let i = 0; i < lettersItemheight.length; i++) {
                    if (value < 0) {
                        this.scrollTo(0);
                    } else if (value > lettersItemheight[i]) {
                        this.scrollTo(i);
                    }
                }

                // console.log(this.mul(sub, myLetters.length));
            },
            onResponderTerminationRequest: (evt, gestureState) => true,
            onPanResponderRelease: (evt, gestureState) => {
                // console.log('抬手 x:' + gestureState.moveX + ',y:' + gestureState.moveY);
            },
            onPanResponderTerminate: (evt, gestureState) => {
                // console.log(`结束 = evt.identifier = ${evt.identifier} gestureState = ${gestureState}`);
            },
        });

    }

    //做一些清除操作 避免再次进入会有数据异常
    componentWillUnmount() {
        myLetters = [];
        myDataBlob = {};//获取到的数据
        mySectionIDs = []; //组id
        myRowIDs = [];//组内
        cityData = [];//获取到的数据
        lettersItemheight = [];
    }


    //渲染
    render() {
        return (
            <View style={styles.container}>
                <NavigationBar
                    onLayout={({nativeEvent: e1}) => this.navigationLayout(e1)}
                    ref={(ref) => this.myNavigationBar = ref}
                    title="选择城市"
                    leftButton={this.getNavLeftBtn()}
                ></NavigationBar>

                <View

                    onLayout={({nativeEvent: e}) => this.searchLayout(e)}
                    style={styles.searchBox}>
                    <Image source={require('../res/image/search_bar_icon_normal.png')} style={styles.searchIcon}/>
                    <TextInput style={styles.inputText}
                               onChangeText={(text) => this.changeText(text)}
                               underlineColorAndroid='transparent' //设置下划线背景色透明 达到去掉下划线的效果
                               placeholder='请输入城市名称或首字母'/>
                </View>
                <ListView
                    contentContainerStyle={styles.contentContainer}
                    ref={listView => this._listView = listView}
                    dataSource={this.state.dataSource}
                    renderRow={this.renderRow}
                    renderSectionHeader={this.renderSectionHeader}
                    enableEmptySections={true}
                    initialListSize={totalNumber}
                    refreshControl={
                        <RefreshControl
                            refreshing={this.state.isLoading}
                            tintColor="#63B8FF"
                            title="正在加载..."
                            titleColor="#63B8FF"
                            colors={['#63B8FF']}
                        />
                    }
                />
                {
                    //判断是否显示右边字母列表
                    this.state.lettersShow == false ? (null) :
                        (   <View
                                ref="ref_letters"
                                {...this._panGesture.panHandlers}
                                onLayout={({nativeEvent: e}) => this.lettersLayout(e)}
                                style={styles.letters}>
                                {myLetters.map((letter, index) => this.renderLetters(letter, index))}
                            </View>
                        )
                }
            </View>
        );
    }
};
const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection: 'column'
    },
    contentContainer: {
        width: width,
        paddingBottom: 20,
        backgroundColor: 'white',
    },
    //字母列表的样式
    letters: {
        flexDirection: 'column',
        position: 'absolute',
        height: height - searchHeight - searchHeight - lettersBottom - StatusBar.currentHeight,
        top: searchHeight + searchHeight + 4,
        bottom: lettersBottom,
        right: 10,
        backgroundColor: 'transparent',
        justifyContent: 'space-between',
        alignItems: 'center',
    },
    // height 字母的高度间距
    // width 字母的宽度
    letter: {
        height: height * 3.3 / 100,
        width: width * 3 / 50,
        justifyContent: 'center',
        alignItems: 'center',
    },
    letterText: {//右边list字母的样式
        textAlign: 'center',
        fontSize: height * 1.1 / 50,
        color: 'black'
    },
    rowdata: {//下划线的样式
        borderBottomColor: '#faf0e6',
        borderBottomWidth: 0.5
    },
    rowdatatext: {
        color: 'gray',
    },
    searchBox: {//最外层搜索框包裹
        height: searchHeight,
        borderColor: 'black',
        flexDirection: 'row',   // 水平排布
        borderRadius: 10,  // 设置圆角边
        backgroundColor: '#FFF',
        borderWidth: 0.8,
        borderRadius: 10,
        borderColor: 'gray',
        alignItems: 'center',
        marginLeft: 8,
        paddingTop: 0,
        marginTop: searchHeightMargin,
        marginBottom: searchHeightMargin,
        paddingBottom: 0,
        marginRight: 8,

    },
    searchIcon: {//搜索图标
        height: 20,
        width: 20,
        marginLeft: 5,
        resizeMode: 'stretch'
    },
    inputText: {//搜索框
        backgroundColor: 'transparent',
        fontSize: 13,
        paddingBottom: 0,
        paddingTop: 0,
        flex: 1,
    },

})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551

Github 地址

右上角点击一下star 就是对我最好的支持 也是我的动力 谢谢

https://github.com/liudao01/cityList