复制AItsuki的Android:SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式
2017-06-05 18:23
776 查看
原文链接:http://blog.csdn.net/u010386612/article/details/50548977
用第一种就解决了问题
BUG修复
2016.01.21 用几部真机测试,发现有些手机,手指没有滑动,move也一直执行。这回导致我们的判断出现一些问题。现在已经修复,加入了TouchSlop判断。
一、前言
急着解决问题的直接看博文的最后面吧,或者点这里跳转过去,正确的解决方式就在那。虽然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; } })1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.onInte 4000 rceptTouchEvent(ev); if (in) { getParent().requestDisallowInterceptTouchEvent(true); this.requestDisallowInterceptTouchEvent(true); } return false; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这两种方法都会导致一个问题, 在ViewPager无法刷新。
就像这样:
第一种方式,偶尔能滑动,偶尔滑不动。为什么会这样,继续往下看,带你分析源码。
第二种方式,连偶尔都不要想,不管在真机还是模拟器,都无法刷新了,这里就不演示了。具体原因请看我的另一篇博客,看懂以后妈妈再也不用担心你的事件分发了。
Android的事件分发源码分析,告别事件冲突
因为事件是先从上层往下层传递的,既然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; }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
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
看不懂的可以再看几遍,主要是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); }1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
当滑动距离大于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); } }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
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
六、后话
不知道说点什么好了,博客有点冷清,是我写的不够好么,还是太难懂_(:з」∠)_
相关文章推荐
- Android:SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式
- Android:SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式
- SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式
- Android中SwipeRefreshLayout和ViewPager左右滑动冲突的原因以及正确的解决方法
- SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式
- Android 中SwipeRefreshLayout与ViewPager滑动事件冲突解决方法
- Android ScrollView嵌套ViewPager滑动失效和无法正常滑动冲突问题解决方案
- Android listview viewpager解决冲突 滑动
- Android listview viewpager解决冲突 滑动
- android 布局 使用 viewPager 时,如何解决 和 子页面 长按滑动 冲突问题
- Android中Viewpager,ScrollView嵌套ViewPager滑动冲突解决
- 解决android中viewpager和内嵌html滑动事件冲突
- 备忘-Android ViewPager 与Gallery滑动冲突解决方法
- Android解决ViewPager嵌套Fragment,Fragment嵌套ViewPager滑动冲突
- Android 跑马灯效果实现的两种方式,解决和viewpager的冲突问题
- Android中解决ListView嵌套Viewpager时,滑动事件冲突的方法
- Android嵌套滑动控件的冲突解决和ViewPager适配当前子控件高度不留空白的办法
- Android中给Listview的HeadView加ViewPager自动轮播图,解决滑动冲突
- MPAndroidChart在ViewPager+Fragment滑动冲突解决
- 完美解决Android里面scrollview嵌套及listview嵌套viewpager的滑动冲突问题的简单方法