ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
***** **事件分发的使用** [TOC=6] # 1.解决事件滑动冲突的思路及方法 ## 1.1常见的三种情况 第一种情况,滑动方向不同 ![](http://ww1.sinaimg.cn/mw690/9fe4afa0gw1f95qa1uuxuj20g00ca0sz.jpg) 第二种情况,滑动方向相同 ![](http://ww4.sinaimg.cn/mw690/9fe4afa0gw1f95q97u2nlj20vc0i7761.jpg) 第三种情况,上述两种情况的嵌套 ![](http://ww2.sinaimg.cn/mw690/9fe4afa0gw1f95qao07c2j20dw0i3q3q.jpg) ## 1.2 解决思路 看了上面三种情况,我们知道他们的共同特点是父View 和子View都想争着响应我们的触摸事件,但遗憾的是我们的触摸事件 同一时刻只能被某一个View或者ViewGroup拦截消费,所以就产生了滑动冲突?那既然同一时刻只能由某一个View或者ViewGroup消费拦截,那我们就只需要 决定在某个时刻由这个 View 或者 ViewGroup 拦截事件,另外的 某个时刻由 另外一个 View 或者 ViewGroup 拦截事件,不就OK了吗?综上,正如 在 《Android开发艺术》 一书提出的,总共 有两种解决方案 以下解决思路来自于 《Android开发艺术》 书籍 下面的两种方法针对第一种情况(滑动方向不同),父View是上下滑动,子View是左右滑动的情况。 ## 1.3 外部解决法 从父View着手,重写onInterceptTouchEvent方法,在父View需要拦截的时候拦截,不要的时候返回false,为代码大概 如下 ~~~ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownPosX = x; mDownPosY = y; break; case MotionEvent.ACTION_MOVE: final float deltaX = Math.abs(x - mDownPosX); final float deltaY = Math.abs(y - mDownPosY); // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (deltaX > deltaY) { return false; } } return super.onInterceptTouchEvent(ev); } ~~~ ## 1.4 内部解决法 从子View着手,父View先不要拦截任何事件,所有的事件传递给 子View,如果子View需要此事件就消费掉,不需要此事件的话就交给 父View处理。 实现思路 如下,重写子 View的dispatchTouchEvent方法,在Action\_down 动作中通过方法 requestDisallowInterceptTouchEvent(true) 先请求 父 View不要拦截事件,这样保证子 View 能够接受到 Action\_move 事件,再在 Action\_move 动作中根据自己的逻辑是否要拦截事件,不需要拦截事件的话再交给 父 View 处理。 ~~~ @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getRawX(); int y = (int) ev.getRawY(); int dealtX = 0; int dealtY = 0; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: dealtX = 0; dealtY = 0; // 保证子View能够接收到Action_move事件 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: dealtX += Math.abs(x - lastX); dealtY += Math.abs(y - lastY); Log.i(TAG, "dealtX:=" + dealtX); Log.i(TAG, "dealtY:=" + dealtY); // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (dealtX >= dealtY) { getParent().requestDisallowInterceptTouchEvent(true); } else { getParent().requestDisallowInterceptTouchEvent(false); } lastX = x; lastY = y; break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev); } ~~~ # 2. ScrollView嵌套ListView或GridView滑动冲突解决方案 开发过程中经常会遇到使用scrollview嵌套listview或gridview的情况,这时由于scrollview拦截消费了滑动事件,所以在listview或gridview区域滑动时该区域无法滑动,而是scrollview整体滑动。 正确的处理应该是当焦点在listview或gridview区域该区域滑动,在区域外则scrollview滑动。 想要解决这个问题,加上如下代码即可: ~~~ listView.setOnTouchListener(new View.OnTouchListener() {   @Override   public boolean onTouch(View arg0, MotionEvent arg1) {     scrollView.requestDisallowInterceptTouchEvent(true); return false;   } } ~~~ ~~~ public class MyListView extends ListView { public MyListView(Context context) { super(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); } public MyListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } } ~~~ # 3. ViewPager嵌套ViewPager滑动冲突解决方案 ## 内部解决法 从子View ViewPager着手,重写 子View的 dispatchTouchEvent方法,在子 View需要拦截的时候进行拦截,否则交给父View处理,代码如下 ~~~ public class ChildViewPager extends ViewPager { private static final String TAG = "xujun"; public ChildViewPager(Context context) { super(context); } public ChildViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int curPosition; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: curPosition = this.getCurrentItem(); int count = this.getAdapter().getCount(); Log.i(TAG, "curPosition:=" +curPosition); // 当当前页面在最后一页和第0页的时候,由父亲拦截触摸事件 if (curPosition == count - 1|| curPosition==0) { getParent().requestDisallowInterceptTouchEvent(false); } else {//其他情况,由孩子拦截触摸事件 getParent().requestDisallowInterceptTouchEvent(true); } } return super.dispatchTouchEvent(ev); } } ~~~ # 4. ScrollView嵌套ViewPager滑动冲突解决方案(了解) ## 4.1 外部解决 从 父View ScrollView着手,重写 OnInterceptTouchEvent方法,在上下滑动的时候拦截事件,在左右滑动的时候不拦截事件,返回 false,这样确保子View 的dispatchTouchEvent方法会被调用,代码 如下 ~~~ /** * @ explain:这个ScrlloView不拦截水平滑动事件, * 是用来解决 ScrollView里面嵌套ViewPager使用的 */ public class VerticalScrollView extends ScrollView { public VerticalScrollView(Context context) { super(context); } public VerticalScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(21) public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } private float mDownPosX = 0; private float mDownPosY = 0; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownPosX = x; mDownPosY = y; break; case MotionEvent.ACTION_MOVE: final float deltaX = Math.abs(x - mDownPosX); final float deltaY = Math.abs(y - mDownPosY); // 这里是否拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (deltaX > deltaY) {// 左右滑动不拦截 return false; } } return super.onInterceptTouchEvent(ev); } } ~~~ ## 4.2 内部解决 通过requestDisallowInterceptTouchEvent(true)方法来影响父View是否拦截事件,我们通过重写ViewPager的 dispatchTouchEvent()方法,在左右滑动的时候请求父View ScrollView不要拦截事件,其他的时候由子View 拦截事件 ~~~ /** * @ explain:这个 ViewPager是用来解决ScrollView里面嵌套ViewPager的 内部解决法的 */ public class MyViewPager extends ViewPager { private static final String TAG = "xujun"; int lastX = -1; int lastY = -1; public MyViewPager(Context context) { super(context); } public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getRawX(); int y = (int) ev.getRawY(); int dealtX = 0; int dealtY = 0; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: dealtX = 0; dealtY = 0; // 保证子View能够接收到Action_move事件 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: dealtX += Math.abs(x - lastX); dealtY += Math.abs(y - lastY); Log.i(TAG, "dealtX:=" + dealtX); Log.i(TAG, "dealtY:=" + dealtY); // 这里是否拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (dealtX >= dealtY) { // 左右滑动请求父 View 不要拦截 getParent().requestDisallowInterceptTouchEvent(true); } else { getParent().requestDisallowInterceptTouchEvent(false); } lastX = x; lastY = y; break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev); } } ~~~