ReactNative 滑动字母选择城市 以及搜索功能
程序员文章站
2022-05-31 08:33:49
...
先上效果图
遇到的问题
- 右侧字母选择器 高度问题,
- 右侧字母选择器 如何使用手势检测panresponse
- 右侧字母选择器 计算高度如何判断是触摸到那个字母上的(思考 如果是==native应用是如何做的==…刚看过 native应用的城市列表也是通过计算每个字母的高度来检测的)动态创建的控件
右边滑动的原理: 通过onlayout计算每个字母高度
,然后加入数组 , 手指触摸字母列表时 知道 触摸的y坐标
这样再减去列表距离顶部的距离 就是字母列表的初始坐标.根据
y坐标和刚才通过onlayout计算出的数组进行比对在那个区间则是
哪一个字母的item
下面上代码
/**
* 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 就是对我最好的支持 也是我的动力 谢谢
上一篇: 搜索功能的实现