引言
使用RN開發(fā)了一段時間,最近遇到了一個比較棘手的問題,就是用react寫個城市選擇列表,當然這個如果用Android原生來寫,網上的例子數不勝數,隨便就能找到,但是react卻很少,也沒有一個和我這個需求相匹配的,所以就只能自己動手擼一個出來咯.
效果
這個城市列表和其他的有點區(qū)別
1,有當前定位城市
2,有熱門城市
3,每個子項是一個類似GridView的效果,而不是ListView
實現的效果圖如下:
實現步驟
- 首先需要一個city.json的json文件
例:
{
"key": "A",
"data": [{
"citys": [
{
"city": "阿拉善盟",
"id": 152900
},
{
"city": "鞍山",
"id": 210300
},
{
"city": "安慶",
"id": 340800
},
{
"city": "安陽",
"id": 410500
},
.....]
}
- 整個列表采用sectionList
SectionList提供了粘性的header,設置為true即可stickySectionHeadersEnabled=true
,這樣在滾動的時候就有實現了粘性效果;
代碼如下:
<SectionList
ref="sectionList"
renderSectionHeader={this.renderSectionHeader}
renderItem={this.renderItem}
stickySectionHeadersEnabled={true}
showsHorizontalScrollIndicator={false}
sections={this.state.sections}
keyExtractor={this._extraUniqueKey}
/>
- 右邊采用ScrollView來實現,最開始采用View,發(fā)現會有事件搶奪問題,具體的原因不祥,畢竟我對RN的事件傳遞也不是特別熟
代碼如下:
/*右側索引*/
sectionItemView() {
const sectionItem = this.state.sections.map((item, index) => {
if (index === 0) {
return null
}
return <Text key={index}
style={
[cityStyle.sectionItemStyle,
{backgroundColor: this.state.isTouchDown ? touchDownBGColor : touchUpBGColor}]
}>
{item.key}
</Text>
});
return (
<ScrollView style={cityStyle.sectionItemViewStyle}
ref="sectionItemView"
onStartShouldSetResponder={() => true}
onMoveShouldSetResponder={() => true}
onResponderTerminationRequest={() => true}
onResponderGrant={this.responderGrant}
onResponderMove={this.responderMove}
onResponderRelease={this.responderRelease}
>
{sectionItem}
</ScrollView>
);
}
這里的幾個方法需要具體說明一下(React Native手勢響應,就和android的onTouchEvent一個意思):
通過實施正確的處理方法,視圖可以成為接觸響應器刻恭。有兩種方法來詢問視圖是否想成為響應器:
- View.props.onStartShouldSetResponder: (evt) => true,——這個視圖是否在觸摸開始時想成為響應器扳碍?
- View.props.onMoveShouldSetResponder: (evt) => true,——當視圖不是響應器時寂嘉,該指令被在視圖上移動的觸摸調用:這個視圖想“聲明”觸摸響應嗎?
如果視圖返回 true 并且想成為響應器瀑凝,那么下述的情況之一就會發(fā)生: - View.props.onResponderGrant:(evt)= > { } ——視圖現在正在響應觸摸事件。這個時候要高亮標明并顯示給用戶正在發(fā)生的事情答憔。
- View.props.onResponderReject:(evt)= > { }——當前有其他的東西成為響應器并且沒有釋放它项栏。
如果視圖正在響應,那么可以調用以下處理程序: - View.props.onResponderMove:(evt)= > { }——用戶正移動他們的手指
- View.props.onResponderRelease:(evt)= > { }——在觸摸最后被引發(fā)嫂用,即“touchUp”
- View.props.onResponderTerminationRequest:(evt)= >true——其他的東西想成為響應器型凳。這種視圖應該釋放應答嗎?返回 true 就是允許釋放
事件處理:
/*用戶手指開始觸摸*/
responderGrant(event) {
this.scrollSectionList(event);
this.setState({
isTouchDown: true,
})
}
/*用戶手指在屏幕上移動手指嘱函,沒有停下也沒有離開*/
responderMove(event) {
console.log("responderMove")
this.scrollSectionList(event);
this.setState({
isTouchDown: true,
})
}
/*用戶手指離開屏幕*/
responderRelease(event) {
console.log("onTouchUp")
this.setState({
isTouchDown: false,
})
}
/*手指滑動甘畅,觸發(fā)事件*/
scrollSectionList(event) {
const touch = event.nativeEvent.touches[0];
// 手指滑動范圍 從 A-Q 范圍從50 到 50 + sectionItemHeight * cities.length
if (touch.pageY >= sectionTopBottomHeight+headerHeight+statusHeight
&& touch.pageY <= statusHeight +headerHeight+sectionTopBottomHeight + sectionItemHeight * 25
&& touch.pageX >= width - sectionWidth
&& touch.pageX <= width
) {
console.log("touchx" + touch.pageX + '.=======touchY' + touch.pageY)
const index = (touch.pageY - sectionTopBottomHeight - headerHeight) / sectionItemHeight;
console.log("index" + index);
if (Math.round(index)>=0&&Math.round(index)<=25){
this.setState({
selectText: this.state.sections[Math.round(index)].key
})
//默認跳轉到 第 index 個section 的第 1 個 item
this.refs.sectionList.scrollToLocation({
animated: true,
sectionIndex: Math.round(index),
itemIndex: 0,
viewOffset: headerHeight
});
}
}
}
這里根據手指在右邊索引欄的滑動事件,獲取到當前的x軸和y軸的具體值,然后計算出具體的子項目的標題,讓SectionList滾動到具體的index位置;
- 子項目列表采用FlatList實現GridView的效果
<FlatList
data={info.section.data[0].citys}
horizontal={false}
numColumns={4}
showsHorizontalScrollIndicator={false}
renderItem={({item}) => this._createItem(item)}
keyExtractor={this._extraUniqueKey2}
/>
這樣基本大體的效果就實現了
最后
React native實現這個有個很坑爹的地方,就是渲染列表會花費很長的時間,Android是這樣,ios沒試過,所以如果沒有渲染完就去滑動索引欄會報這個scrolltoindex-should-be-used-in-conjunction-with-getitemlayout-or-on
異常,網上找了很多資料,說是SectionList沒有渲染完就調用scrollToLocation,如果要在沒有渲染完之前調用scrollToLocation需要配合getitemlayout使用,但是這個getitemlayout又需要傳入具體item的高度,然而我的FlatList的高度是不確定的,所以就很坑爹了,找不到辦法解決,只能延時加載右邊索引欄;
代碼如下:
componentDidMount() {
setTimeout(() => {
this.setState({
canTouch: true
})
}, 1600)
}
這樣全部基本就完成了
項目地址:
https://github.com/mouxuefei/RN-CitySectionList