ReactNative實現城市選擇列表

引言

使用RN開發了一段時間,最近遇到了一個比較棘手的問題,就是用react寫個城市選擇列表,固然這個若是用Android原生來寫,網上的例子數不勝數,隨便就能找到,可是react卻不多,也沒有一個和我這個需求相匹配的,因此就只能本身動手擼一個出來咯.react

效果

這個城市列表和其餘的有點區別android

1,有當前定位城市 2,有熱門城市 3,每一個子項是一個相似GridView的效果,而不是ListView 實現的效果圖以下:ios

實現步驟

  • 首先須要一個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,這樣在滾動的時候就有實現了粘性效果;

代碼以下:git

<SectionList
                        ref="sectionList"
                        renderSectionHeader={this.renderSectionHeader}
                        renderItem={this.renderItem}
                        stickySectionHeadersEnabled={true}
                        showsHorizontalScrollIndicator={false}
                        sections={this.state.sections}
                        keyExtractor={this._extraUniqueKey}
                    />
複製代碼
  • 右邊採用ScrollView來實現,最開始採用View,發現會有事件搶奪問題,具體的緣由不祥,畢竟我對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一個意思): 經過實施正確的處理方法,視圖能夠成爲接觸響應器。有兩種方法來詢問視圖是否想成爲響應器:github

  • View.props.onStartShouldSetResponder: (evt) => true,——這個視圖是否在觸摸開始時想成爲響應器?
  • View.props.onMoveShouldSetResponder: (evt) => true,——當視圖不是響應器時,該指令被在視圖上移動的觸摸調用:這個視圖想「聲明」觸摸響應嗎? 若是視圖返回 true 而且想成爲響應器,那麼下述的狀況之一就會發生:
  • View.props.onResponderGrant:(evt)= > { } ——視圖如今正在響應觸摸事件。這個時候要高亮標明並顯示給用戶正在發生的事情。
  • View.props.onResponderReject:(evt)= > { }——當前有其餘的東西成爲響應器而且沒有釋放它。 若是視圖正在響應,那麼能夠調用如下處理程序:
  • View.props.onResponderMove:(evt)= > { }——用戶正移動他們的手指
  • View.props.onResponderRelease:(evt)= > { }——在觸摸最後被引起,即「touchUp」
  • View.props.onResponderTerminationRequest:(evt)= >true——其餘的東西想成爲響應器。這種視圖應該釋放應答嗎?返回 true 就是容許釋放

事件處理:json

/*用戶手指開始觸摸*/
    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,
        })
    }
 /*手指滑動,觸發事件*/
    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位置;bash

  • 子項目列表採用FlatList實現GridView的效果
<FlatList
                    data={info.section.data[0].citys}
                    horizontal={false}
                    numColumns={4}
                    showsHorizontalScrollIndicator={false}
                    renderItem={({item}) => this._createItem(item)}
                    keyExtractor={this._extraUniqueKey2}
                />
複製代碼

這樣基本大致的效果就實現了ui

最後

React native實現這個有個很坑爹的地方,就是渲染列表會花費很長的時間,Android是這樣,ios沒試過,因此若是沒有渲染完就去滑動索引欄會報這個scrolltoindex-should-be-used-in-conjunction-with-getitemlayout-or-on異常,網上找了不少資料,說是SectionList沒有渲染完就調用scrollToLocation,若是要在沒有渲染完以前調用scrollToLocation須要配合getitemlayout使用,可是這個getitemlayout又須要傳入具體item的高度,然而個人FlatList的高度是不肯定的,因此就很坑爹了,找不到辦法解決,只能延時加載右邊索引欄; 代碼以下:this

componentDidMount() {
        setTimeout(() => {
            this.setState({
                canTouch: true
            })
        }, 1600)
    }
複製代碼

這樣所有基本就完成了 項目地址: github.com/mouxuefei/R…spa

相關文章
相關標籤/搜索