Android實踐之ScrollView中滑動衝突處理

來源:互聯網
上載者:User

標籤:3.2   action   move   重要   版本   部分   abs   last   ret   

轉載註明出處:http://blog.csdn.net/xiaohanluo/article/details/52130923

1. 前言

       在Android開發中,假設是一些簡單的布局。都非常easy搞定。可是一旦涉及到複雜的頁面,特別是為了相容小屏手機而使用了ScrollView以後,就會出現非常多點擊事件的衝突。最經典的就是ScrollView中嵌套了ListView。

我想大部分剛開始接觸Android的同學們都踩到過這個坑,這一篇文章就從近期做的一個項目講起。然後在過程中提供一些解決衝突的思路。

2. 項目起始

       項目有一個頁面,涉及到了ViewPager。MapView。ListView,也就是說在一個頁面中,會有這三個View,非常明顯,螢幕無法全然顯示,須要ScrollView來做一下支援,就引入了ScrollView作為外層的容器。

可是由於這個頁面的資料展示須要做到使用者手動下拉重新整理,於是又引入了官方的SwipeRefreshLayout。


於是這個頁面的布局就成了這樣子。

例如以(細節布局忽略)。


圖-1 布局圖

       增加了ScrollView和SwipeRefreshLayout之後引入了新的問題。就是各個控制項之間的事件衝突,嵌套在ScrollView中的ViewPager、MapView、ListView都須要能夠正確的處理點擊事件,特別是ListView。需求要求它在ScrollView中能夠滑動,兩種滑動混淆在一起,不是特別優點理。

問題提出來了,以下直接看解決思路。

3. 解決滑動衝突的思路

       在ViewGroup中有個方法叫requestDisallowInterceptTouchEvent(boolean disallowIntercept),這種方法能夠用來控制該ViewGroup是否截斷點擊事件。我們解決滑動衝突的時候,事實上就是在某個時機去調用這種方法。讓父布局不截斷點擊事件,將點擊事件傳遞到子View。讓相關的子View去處理。
       以下就是關於在項目中處理各種點擊事件衝突的一些範例和思考。處理的方法僅僅是提供一種思路,可能並非最優的方法,肯定存在其它思路的解決方式。
       以下處理滑動衝突的方案都是在子View的OnTouchListener裡面進行處理。並沒有去複寫控制項的點擊事件處理過程。在使用中還是比較方便的。

3.1 MapView地圖頁面滑動衝突

       MapView與ScrollView的衝突主要在於,當使用者點擊到MapView地圖並且滑動的時候。希望由地圖Map去處理點擊事件。並包含興許的滑動事件、雙手指縮放地圖等等。


       在ScrollView中,是會預設截斷點擊事件的,導致使用者點擊到地圖後,地圖基本是沒有反應。更別談雙手指縮放地圖了。
       使用者手指點擊到地圖,並且滑動的時候,非常難確定使用者是要ScrollView上下滑動還是操控地圖內容滑動,所以我簡單的覺得,僅僅要使用者手指點擊到地圖。就是要對地圖進行操作。當使用者手指抬起。就覺得使用者不須要操作地圖了。
       解決思路也非常簡單。就是在使用者點擊到地圖或者滑動地圖時候,讓ScrollView不截斷點擊事件。並傳遞給子View處理。也就是地圖去處理點擊事件;當使用者手指抬起的時候,將ScrollView的狀態恢複至之前的狀態。也就是ScrollView能夠截斷點擊事件。

我使用的是百度地圖,直接上代碼,更easy理解。

mMapView.getChildAt(0).setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                if(event.getAction() == MotionEvent.ACTION_UP){                    //同意ScrollView截斷點擊事件,ScrollView可滑動                    mScrollView.requestDisallowInterceptTouchEvent(false);                }else{                    //不同意ScrollView截斷點擊事件,點擊事件由子View處理                    mScrollView.requestDisallowInterceptTouchEvent(true);                }                return false;            }        });
3.2 ViewPager滑動衝突解決

       在這個項目中,ViewPager在頁面最頂層。假設僅僅是ScrollView裡面嵌套了ViewPager。由於這兩個控制項是不同方向的滑動事件,所以基本不會出現衝突。


       可是由於引入了SwipeRefreshLayout,我發如今滑動ViewPager的時候。非常easy就觸發了SwipeRefreshLayout的下來重新整理,進而有可能阻斷了ViewPager的左右滑動效果,體驗非常不好。並且在滑動ViewPager的過程中,使用者滑動肯定不是一直水平的,會有一定程度向上向下的滑動。
       ViewPager處理衝突和地圖處理衝突有些不同,由於當使用者點擊到ViewPager,在滑動過程中。基本就能夠推測到使用者是想左右滑動ViewPager還是上下滑動ScrollView(或者下拉重新整理),這就不能像地圖一樣。在點擊到ViewPager就禁止ScrollView截斷點擊事件(或者SwipeRefreshLayout下拉重新整理功能),須要在滑動過程中做出推斷。


       解決思路就是,設定一個閾值,一旦使用者在X軸也就是橫向滑動距離超過這個閾值,我就覺得使用者是要左右滑動ViewPager。就禁止ScrollView截斷點擊事件同一時候設定SwipeRefreshLayout不能下拉重新整理。當使用者抬起手指,就覺得使用者對ViewPager的操作已經完成,將ScrollView和SwipeRefreshLayout狀態恢複。

mViewPager.setOnTouchListener(new View.OnTouchListener() {    @Override    public boolean onTouch(View v, MotionEvent event) {        int action = event.getAction();        if(action == MotionEvent.ACTION_DOWN) {            // 記錄點擊到ViewPager時候,手指的X座標            mLastX = event.getX();        }        if(action == MotionEvent.ACTION_MOVE) {            // 超過閾值            if(Math.abs(event.getX() - mLastX) > 60f) {                mRefreshLayout.setEnabled(false);                mScrollView.requestDisallowInterceptTouchEvent(true);            }        }        if(action == MotionEvent.ACTION_UP) {            // 使用者抬起手指,恢複父布局狀態            mScrollView.requestDisallowInterceptTouchEvent(false);            mRefreshLayout.setEnabled(true);        }        return false;    }});

       使用者點擊到ViewPager時候,記錄下點擊位置的X座標,當使用者滑動過程中,假設在X軸上面的滑動超過閾值(我寫的是60f,這個能夠在實際使用中自行設定最佳的閾值)。就禁止ScrollView截斷點擊事件,同一時候設定不可下拉重新整理。當使用者手指離開螢幕。將ScrollView和SwipeRefreshLayout的狀態恢複。

3.3 ListView滑動衝突解決

       在ScrollView中嵌套ListView,會出現各種各樣奇怪的問題。比方說ListView顯示有問題,可能才一兩個Item那麼高。並沒有全然的展開。

網上流傳解決這種問題的方法會有兩種。

  • 依據展示資料的個數乘以每個Item的高度。計算出ListView的整體高度,然後動態設定ListView的高度
  • 複寫ListView的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,讓ListView全然展開

       這兩種方法都能夠解決ListView展示不全然的問題。並且也能夠滑動(事實上是使用ScrollView的滑動效果),可是有一個最大的遺憾。就是ListView裡面的View不能複用了。由於這兩種方法都是算出了ListView的所有高度,然後將ListView控制項的高度設定成這個高度。這種話,ListView就相當於一個LinearLayout的布局了。失去了複用View的優勢。並且在某些情境可能還沒有LinearLayout好用。更甚的是,假設有大量圖片的話,非常easy就OOM了。這是在研發過程中最不希望看見的。


       能夠參考一下美團,美團的首頁。就是一個ScrollView,下滑的時候會發現,並不能無限向下滑動,到了底部會提醒跳轉到一個二級頁面去查看所有的團購資訊。這是處理ScrollView裡面嵌套相似ListView列表布局的時候的一種解決方式。
可是在我遇見的這個項目裡面,並不能這樣處理。
       上面的提到的兩種解決思路非常明白,假設想要ListView正常展示就須要確定ListView的高度,這個非常重要。


       所以首先。我須要在布局檔案裡設定ListView的高度。是一個明白的數值。設定高度之後。假設ListView中的資料的Item總高度超過ListView所設定的高度,就能夠複用View了。可是這僅僅是攻克了ListView的顯示問題,ListView與ScrollView的滑動衝突,並沒有解決。
       要解決滑動的衝突,最基本的是確定禁止ScrollView截斷點擊事件的時機,然後來分析有哪些時機。

  • ScrollView在未滑動究竟部時候,假設點擊並滑動ListView時候。ListView是不能滑動的。不禁止。

  • 假設ScrollView滑動究竟部,且ListView已經到頂部,繼續下拉ListView,事實上會拉動ScrollView,不禁止。

  • 假設ScrollView滑動究竟部,使用者向上滑。ListView滑動,禁止ScrollView截斷點擊事件能力

       非常明顯,在推斷禁止ScrollView截斷點擊事件時機的時候,須要知道ScrollView是否滑動到了底部。於是。重寫了ScrollView的ScrollChanged()方法,來推斷ScrollView是否滑動究竟部(SDK API 23版本號碼中ScrollView能夠設定setOnScrollChangeListener()來監聽滑動的變化,可是之前版本號碼不支援。為了相容。自己須要重寫)。

@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt){    super.onScrollChanged(l,t,oldl,oldt);    // 滑動的距離加上本身的高度與子View的高度對照    if(t + getHeight() >=  getChildAt(0).getMeasuredHeight()){        // ScrollView滑動究竟部        if(mOnScrollToBottomListener != null) {            mOnScrollToBottomListener.onScrollToBottom();        }    } else {        if(mOnScrollToBottomListener != null) {            mOnScrollToBottomListener.onNotScrollToBottom();        }    }}public void setScrollToBottomListener(OnScrollToBottomListener listener) {    this.mOnScrollToBottomListener = listener;}public interface OnScrollToBottomListener {    void onScrollToBottom();    void onNotScrollToBottom();}

有了思路,並且ScrollView滑動究竟部的標識也能夠拿到,以下就能夠直接來解決滑動衝突了。直接看代碼。

mScrollView.setScrollToBottomListener(new BottomScrollView.OnScrollToBottomListener() {    @Override    public void onScrollToBottom() {        isSvToBottom = true;    }    @Override    public void onNotScrollToBottom() {        isSvToBottom = false;    }});mListView.setOnTouchListener(new View.OnTouchListener() {    @Override    public boolean onTouch(View v, MotionEvent event) {        int action = event.getAction();        if(action == MotionEvent.ACTION_DOWN) {            mLastY = event.getY();        }        if(action == MotionEvent.ACTION_MOVE) {            int top = mListView.getChildAt(0).getTop();            float nowY = event.getY();            if(!isSvToBottom) {                // 同意scrollview攔截點擊事件, scrollView滑動                mScrollView.requestDisallowInterceptTouchEvent(false);            } else if(top == 0 && nowY - mLastY > THRESHOLD_Y_LIST_VIEW) {                // 同意scrollview攔截點擊事件, scrollView滑動                mScrollView.requestDisallowInterceptTouchEvent(false);            } else {                // 不同意scrollview攔截點擊事件, listView滑動                mScrollView.requestDisallowInterceptTouchEvent(true);            }        }        return false;    }});

       相對於其它的控制項來說,ListView和ScrollView之間的滑動衝突更難解決,但事實上在實際使用中並不推薦ScrollView裡面嵌套ListView,一旦業務複雜,非常easy出現各種UI和商務邏輯衝突的錯誤。

4. 執行效果

由於地圖增加比較麻煩。所以在Demo中並沒有引入地圖。看一下執行效果。



圖-2 執行效果5. 總結

       本篇文章僅僅是提供一種解決方案的思路。在詳細的情境下,互動往往是貼合詳細業務需求的。可是無論怎麼樣,找出點擊事件截斷和處理的時機是最重要的,環繞這個關鍵點,總能找出對應的解決方案。

附上Demoproject地址:Demoproject地址連結

Android實踐之ScrollView中滑動衝突處理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.