Android:SwipeRefreshLayout和ViewPager滑動衝突的原因和正確的解決方式
BUG修復
2016.01.21 用幾部真機測試,發現有些手機,手指沒有滑動,move也一直執行。這回導致我們的判斷出現一些問題。現在已經修復,加入了TouchSlop判斷。
2017.6.16 修改文章中的一些錯誤
一、前言
急著解決問題的直接看博文的最後面吧,或者點這裡跳轉過去,正確的解決方式就在那。
雖然SwipeRefreshLayout出來已經很久了,但是知道今天我才第一次使用。
然後發現兩個問題:
1. SwipeRefreshLayout會吃掉ViewPager的滑動事件。
2. SwipeRefreshLayout需要套在ScrollView和ListView上的時候才表現的比較友好,在其他ViewGroup上有點問題,不知道為什麼,到時候去看下原始碼。
今天我只說第一個問題:
很明顯如果是往左下或右下滑動的時候,事件就會被SwipeRefreshLayout吃掉。但是平移滑動或者往右上左上滑動就沒問題。
二、目前網上流傳的解決方式
我網上找解決方法的時候,發現無非都是兩種方式。
1、監聽ViewPager的OnTouch事件,滑動的時候禁用swipeRefreshLayout
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event ) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mSwipeRefreshLayout.setEnabled(false);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mSwipeRefreshLayout.setEnabled(true);
break ;
}
return false;
}
})
2、繼承ViewPager,請求父控制元件不要攔截ViewPager事件
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean in = super.onInterceptTouchEvent(ev);
if (in) {
getParent().requestDisallowInterceptTouchEvent(true);
this.requestDisallowInterceptTouchEvent(true);
}
return false;
}
}
這兩種方法都會導致一個問題, 在ViewPager無法重新整理。
就像這樣:
第一種方式,偶爾能滑動,偶爾滑不動。為什麼會這樣,繼續往下看,帶你分析原始碼。
第二種方式,連偶爾都不要想,不管在真機還是模擬器,都無法重新整理了,這裡就不演示了。具體原因請看我的另一篇部落格,看懂以後媽媽再也不用擔心你的事件分發了。
Android的事件分發原始碼分析,告別事件衝突
————2017.06.16————
隨著版本更新,android的事件分發的機制也原來越完善,老的文章已經不適合了,我已經不知道是我當時寫錯了還是SwipeRefreshLayout更改了,下面補充下第二種方式。
這裡要感謝一下28樓的”GEASS123”網友的提醒.
第二種方式:
第二種方式不起作用的原因是,SwipeRefreshLayout重寫了requestDisallowInterceptTouchEvent方法
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// if this is a List < L or another view that doesn't support nested
// scrolling, ignore this request so that the vertical scroll event
// isn't stolen
if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
// Nope.
} else {
super.requestDisallowInterceptTouchEvent(b);
}
}
因為事件是先從上層往下層傳遞的,既然ViewPager的事件被吃掉了,那麼肯定是在SwipeRefreshLayout中被消費了。
我們去看看SwipeRefreshLayout的原始碼。
1. 先看dispatch方法,發現重寫此方法。
2. 然後看onIntercept方法,發現是在這裡攔截了。那麼onTouchEvent方法就不用看了。下面我們就來分析一下onInterceptTouchEvent方法的原始碼。
三、SwipeRefreshLayout的onInterceptTouchEvent原始碼分析。
有目的性的分析,我們只需要分析和事件衝突相關的原始碼,所以只註釋的關鍵部分。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 確保有SwipeRefreshLayout有Target
// 遍歷所有child,第一個child就是target(除了重新整理的那個圈)。
// 這就是為啥SwiperefreshLayout只能有一個child的原因。
// 先無視掉這句程式碼,和我們分析目的無關
ensureTarget();
final int action = MotionEventCompat.getActionMasked(ev);
// 這個也無視吧, mReturningToStart一直都是false的,原始碼中並沒有賦值
// 估計原本用於判斷是否正在重新整理中,後來用了其他方式判斷。(猜測)
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
// 一個記錄是否正在進行拖拽的標記,初始化false。
mIsBeingDragged = false;
// 獲取按下的Y軸位置
final float initialDownY = getMotionEventY(ev, mActivePointerId);
if (initialDownY == -1) {
return false;
}
mInitialDownY = initialDownY;
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
}
// 獲取當前的Y軸位置
final float y = getMotionEventY(ev, mActivePointerId);
if (y == -1) {
return false;
}
// 獲取手指在Y軸的滑動距離
final float yDiff = y - mInitialDownY;
// 如果滑動距離大於mTouchSlop(不同手機的值不同,一般為8px)
// 並且當前不是在拖拽中
if (yDiff > mTouchSlop && !mIsBeingDragged) {
mInitialMotionY = mInitialDownY + mTouchSlop;
// 設定當前拖拽標記為true
mIsBeingDragged = true;
mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
}
break;
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//當手指擡起的時候設定拖拽標記為false;
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
break;
}
// 如果是拖拽中,攔截事件,否則不攔截。
return mIsBeingDragged;
}
看不懂的可以再看幾遍,主要是mIsBeingDragged這個引數的值是否為true。
四、使用第一種方式,偶爾能拉下小球的原因
1、那麼我們來分析下,為什麼使用第一種方式的時候,偶爾將小球給拉下來。
首先看這裡
// 獲取手指在Y軸的滑動距離
final float yDiff = y - mInitialDownY;
// 如果滑動距離大於mTouchSlop(不同手機的值不同,一般為8px)
// 並且當前不是在拖拽中
if (yDiff > mTouchSlop && !mIsBeingDragged) {
mInitialMotionY = mInitialDownY + mTouchSlop;
// 設定當前拖拽標記為true
mIsBeingDragged = true;
mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
}
當滑動距離大於mTouchSlop的時候才攔截事件。
也就是說
- 如果我Y軸滑動距離沒有大於這個mTouchSlop,mIsBeingDragged為false,事件就不攔截了,會繼續往下分發,那麼ViewPager就響應到了move事件,並且將SwipeRefreshLayout設定成Disable了。這就是為什麼往下滑動為什麼總是不能將小球拉下來的原因。
- 如果Y軸滑動距離大於這個mTouchSlop,那麼事件就攔攔截了自己處理,小球就可以被拉下來了。這也是偶爾能將小球拉下來的原因。
什麼時候Y軸滑動距離會大於mTouchSlop而不被ViewPager響應到事件呢。
要知道兩次Touch之間也是有個很短的響應時間的,只要在這個時間內,Y軸滑動距離大於mTouchSlop就可以了,這時候事件就被攔截了,ViewPager沒機會響應到move事件,從而不會禁用掉SwipeRefreshLayout。
我們來測試一下,超級快速的往下滑動。
可以看到,慢慢滑動的時候,小球無法拉下來,如果快速下拉,小球就出來了。
這也是因為在模擬器上比較卡的原因,如果在真機上,要更快一些才可以。
五、解決方式
寫了一大堆有的沒的才到了重點,彆著急,我覺得看完上面內容會對以後解決相關問題會有幫助,百度谷歌也不是所有問題都能搜的出來。
重寫SwipeRefreshLayout的onIntercept方法就可以很簡單的解決了。
思路:
1. 因為下拉重新整理,只有縱向滑動的時候才有效,那麼我們就判斷此時是縱向滑動還是橫向滑動就可以了。
2. 縱向滑動就攔截事件,橫向滑動不攔截。
3. 怎麼判斷是縱向滑動還是橫向滑動,只要判斷Y軸的移動距離大於X軸的移動距離那麼就判定為縱向滑動就行了。
以下就是重寫後的SwipeRefreshLayout,直接複製到專案就可以使用了。
/**
* Created by AItsuki on 2016/1/20.
*/
public class VpSwipeRefreshLayout extends SwipeRefreshLayout {
private float startY;
private float startX;
// 記錄viewPager是否拖拽的標記
private boolean mIsVpDragger;
private final int mTouchSlop;
public VpSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 記錄手指按下的位置
startY = ev.getY();
startX = ev.getX();
// 初始化標記
mIsVpDragger = false;
break;
case MotionEvent.ACTION_MOVE:
// 如果viewpager正在拖拽中,那麼不攔截它的事件,直接return false;
if(mIsVpDragger) {
return false;
}
// 獲取當前手指位置
float endY = ev.getY();
float endX = ev.getX();
float distanceX = Math.abs(endX - startX);
float distanceY = Math.abs(endY - startY);
// 如果X軸位移大於Y軸位移,那麼將事件交給viewPager處理。
if(distanceX > mTouchSlop && distanceX > distanceY) {
mIsVpDragger = true;
return false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 初始化標記
mIsVpDragger = false;
break;
}
// 如果是Y軸位移大於X軸,事件交給swipeRefreshLayout處理。
return super.onInterceptTouchEvent(ev);
}
}
相關推薦
Android:SwipeRefreshLayout和ViewPager滑動衝突的原因和正確的解決方式
BUG修復 2016.01.21 用幾部真機測試,發現有些手機,手指沒有滑動,move也一直執行。這回導致我們的判斷出現一些問題。現在已經修復,加入了TouchSlop判斷。 2017.6.16 修改文章中的一些錯誤 一、前言 急著解決問題的直接看博
複製AItsuki的Android:SwipeRefreshLayout和ViewPager滑動衝突的原因和正確的解決方式
原文連結:http://blog.csdn.net/u010386612/article/details/50548977 用第一種就解決了問題 BUG修復 2016.01.21 用幾部真機測試,發現有些手機,手指沒有滑動,move也一直執行。這回導致我們的判斷出現
SwipeRefreshLayout和ViewPager滑動衝突的原因和正確的解決方式
出處http://blog.csdn.net/u010386612 問題: 1. SwipeRefreshLayout會吃掉ViewPager的滑動事件。 2. SwipeRefreshLayout需要套在ScrollView和ListView上的時候才表現的比較友好
Android--關於ListView和ViewPager滑動衝突的處理
在專案中我們經常會做在listview的header新增viewpager來滾動播放(廣告或者新聞等焦點圖)的需求,由於viewpager的橫向滑動和listview的縱向滑動會導致衝突 解決辦法如下: 1,繼承viewpager並且重寫dispatchTo
Android項目實戰(十五):自定義不可滑動的ListView和GridView
con app lis androi color max XP xtend exp 原文:Android項目實戰(十五):自定義不可滑動的ListView和GridView不可滑動的ListView (RecyclweView類似) public class NoSc
Android Viewpager巢狀Viewpager滑動衝突。
場景:tablayout繫結viewpager,viewpager巢狀fragement,其中一個fragment中嵌套了一個子viewpager,導致不能正常滑動。 解決方法: 重寫viewpager的canScroll()方法。 @Override protected bo
Android關於CoordinatorLayout和ListView滑動衝突的解決(上滑ToolBar隱藏,下滑出現)
最近專案中使用到了CoordinatorLayout這種佈局方式,搭配RecycleView,實現起來比較簡單,而且不用自己處理滑動事件,但是改為了ListView後發生了滑動衝突. 所以想到了以下解決方案: 1.使用事件分發,當ListView在Y軸滑動時,將事件交給C
Android成長實戰系列文章之ListView Item和Button點選事件的衝突原因和解決方案
筆者熱衷於技術,也是一名在Android方向上滾爬的程式設計師,以下是我技術總結系列文章: 此係列文章屬於Android成長實戰系列,主要以專案中實際用到的東西分享出來,更注重於實戰程式設計能力的培養。 在我們實際專案開發過程中難免遇到各種事件分發有關問題,
Android 跑馬燈效果實現的兩種方式,解決和viewpager的衝突問題
第一篇部落格寫一點簡單的東西,多麼的簡單,就是一個跑馬燈,簡單的要死。所以話不多,寫起來。首先我們來講一個段子先緩和一下氣氛。大學的時候,隔壁宿舍老喜歡蹭我的wifi,煩死了,然後我把wifi名改成了:206有個帥哥是誰。密碼是我的名字,媽的看誰還蹭,結果。除了
android-Ultra-Pull-To-Refresh重新整理框架與viewpager滑動衝突解決方案
文章概述: 問題描述: liaohuqiu 開源的 android-Ultra-Pull-To-Refresh 下拉重新整理框架,在使用時,會經常遇到巢狀banner的使用場景,即:子ViewGroup巢狀ViewPager使用,例如: <c
SwipeRefreshLayout與ViewPager滑動事件衝突解決
問題描述:開發中發現,SwipeRefreshLayout的下拉重新整理,與ViewPager開發的banner的左右滑動事件有一點衝突,導致banner的左右滑動不夠順暢。很容易在banner的左右滑動的過程中,觸發SwipeRefreshLayout的下拉重新整理,從而導
android 基礎知識View (一)滑動衝突攔截和原理
自定義View滑動衝突現象: 第一種是同向,第二種為異向,第三種為前兩種的組合模式 滑動衝突解決方案: 首先決定x和y移動方向的長度來決定是x還是y的移動 第一外部攔截法: @Override public boolean onI
SwipeRefreshLayout與ViewPager滑動事件沖突解決
over 能夠 pre touch listener cti pan out 觸發 問題描寫敘述:開發中發現,SwipeRefreshLayout的下拉刷新,與ViewPager開發的banner的左右滑動事件有一點沖突,導致banner的左右滑動不夠順暢。非常easy在
“錯誤 1067:進程意外終止”的原因和解決方案
解決方案 microsoft bubuko buffer 圖片 src mysq mic ODB 錯誤原因: 1、mysql的配置文件裏面的innodb_buffer_pool_size的值設置太大了。 解決方案: 1、innodb_buffer_pool
Android事件分發機制以及滑動衝突處理
轉載請註明出處:http://blog.csdn.net/u013038616/article/details/50733811 方便日後的檢視與交流,將學習與實踐總結如下。 一、Android事件傳遞分析 1、ViewGroup中事件分發機制相關的方法 a、dispatchTo
Android中TextView居中顯示無效的原因和解決方案。
今天在寫程式碼的時候,出現一個Bug,最後解決了,現在記錄下。 自定義了一個佈局控制元件,用於PopupWindow提示郵箱型別,結果顯示的郵箱型別無法居中,剛開始的效果如圖所示: 上面所貼圖片沒有經過處理,其中“@126.com”的文字在白色背景中沒有居中。注:白色
NestedScrollView與Viewpager滑動衝突
最近實現需求Viewpager實現載入視訊和圖片實現輪播,所實現的介面需要巢狀NestedScrollView,所出現的問題就是:Viewpager可以實現自動輪播,但是不能實現手動輪播,這是我很鬱悶,一想肯定是滑動衝突了,網上也找了很多的解決辦法。後邊得到 NestedScrollView
解決SwipeRefreshLayout與ScrollView滑動衝突
在頁面為了相容小螢幕裝置我們需要巢狀一個ScrollView來讓我們的佈局可以滑動,此時恰好外層使用了SwipeRefreshLayout那滑動衝突就來了,下面給出解決辦法 1.方法一:使用NestedScrollView替換ScrollView <android.
appbarlayout+springview+viewpager滑動衝突的問題
簡介:在做一個關於CoordinatorLayout+appbarlayout實現滑動隱藏Toolbar的demo的時候,裡面加了viewpager+fragment,重新整理控制元件用的springview,滑動用的recyclerview發現左右滑動的時候,toolbar已經隱藏了
android頭部導航跟隨viewpager滑動
由於專案需要,就自己搜尋了一下類似的demo。參考了這個連結:http://www.cnblogs.com/and_he/archive/2012/03/31/2426248.html但是發現作者給的下載地址不能用,我就按照作者思路自己寫了個demo。新手有不好的地方請大家