外部解决法
从父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); }
内部解决法
从子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); }
更多细节,可以查看我的这篇文章,里面有详细介绍哦
ViewPager,ScrollView 嵌套ViewPager滑动冲突解决
View 双击,多击事件是怎么实现的
实现之前,我们首先来阐述一下思路,怎样实现双击事件,正所谓,授人以鱼不如授人以渔。
单击:用户点击一次之后,一段时间之内不再点击
双击;用户点击一次之后,一段时间之内再次点击
实现思路
- 我们监听 onTouch 事件,在 ACTION_DOWN 的时候,点击次数 clickCount +1;
- 同时,在 ACTION_DOWN 的时候,延时一段时间,执行相应的 Runnable 任务,这里我们用 handler 的 postDelayed 实现
- 在延时任务执行的时候,我们根据点击的次数,进行单击或者多级的回调,最后,记得重置点击次数,以及移除延时任务
open class MyDoubleTouchListener(private val myClickCallBack: MyClickCallBack) : OnTouchListener { private var clickCount = 0 //记录连续点击次数 private val handler: Handler = Handler() interface MyClickCallBack { fun oneClick() //点击一次的回调 fun doubleClick() //连续点击两次的回调 } override fun onTouch(v: View, event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_DOWN) { clickCount++ handler.postDelayed({ if (clickCount == 1) { myClickCallBack.oneClick() } else if (clickCount == 2) { myClickCallBack.doubleClick() } handler.removeCallbacksAndMessages(null) //清空handler延时,并防内存泄漏 clickCount = 0 //计数清零 }, timeout.toLong()) //延时timeout后执行run方法中的代码 } return false //让点击事件继续传播,方便再给View添加其他事件监听 } companion object { private const val TAG = "MyClickListener" private val timeout = ViewConfiguration.getDoubleTapTimeout() //双击间四百毫秒延时 init { Log.i(TAG, "timeout is $timeout ") } } }
三击事件
三级事件呢,其实也很简单,我们直接判断在指定时间间隔内点击的次数即可
open class MyMultiTouchListener(private val myClickCallBack: MyClickCallBack) : OnTouchListener { private var clickCount = 0 //记录连续点击次数 private val handler: Handler = Handler() interface MyClickCallBack { fun oneClick() //点击一次的回调 fun doubleClick() //连续点击两次的回调 fun threeClick() // 连续点击三次的回调 } override fun onTouch(v: View, event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_DOWN) { clickCount++ handler.postDelayed({ if (clickCount == 1) { myClickCallBack.oneClick() } else if (clickCount == 2) { myClickCallBack.doubleClick() } else if (clickCount == 3) { myClickCallBack.threeClick() } handler.removeCallbacksAndMessages(null) //清空handler延时,并防内存泄漏 clickCount = 0 //计数清零 }, timeout.toLong()) //延时timeout后执行run方法中的代码 } return false //让点击事件继续传播,方便再给View添加其他事件监听 } companion object { private const val TAG = "MyClickListener" private val timeout = 600 //双击间四百毫秒延时 init { Log.i(TAG, "timeout is $timeout ") } } }
手势识别
在 Android 开发当中,几乎所有的事件都会与用户进行交互,而我们用得的最多的就是手势了。
我们知道当我们触摸屏幕的时候,会产生很多事件,比如 down,move,up, fling 事件等等。一些简单的处理,我们可以直接重写 View 的 onTouchEvent 方法,根据 View 的 MotionEvent 事件进行处理。
而 Google 为了方便开发者方便接入,提供了几个默认处理类,那就是 GestureDetector 和 ScaleGestureDetector。
GestureDetector这个类对外提供了两个接口和一个外部类
。
接口:OnGestureListener,OnDoubleTapListener
内部类:SimpleOnGestureListener,同时实现了 OnGestureListener,OnDoubleTapListener 接口,如果只想使用接口里面的某个方法,可以直接使用它,方便快捷。
讲解之前,我们向来看一下怎么使用
GestureDetector(Context context, GestureDetector.OnGestureListener listener)
GestureDetector 基本使用
第一步,初始化 GestureDetector 对象
mDetector = GestureDetectorCompat(this, MyGestureListener())
可以看到有两个参数,第一个参数 context,第二个参数 OnGestureListener,我们可以直接实现 OnGestureListener 接口,也可以直接使用 GestureDetector.SimpleOnGestureListener
private class MyGestureListener : GestureDetector.OnGestureListener { private val TAG = "GestureDemoActivity" override fun onShowPress(e: MotionEvent?) { Log.d(TAG, "onShowPress: e is $e") } override fun onSingleTapUp(e: MotionEvent?): Boolean { Log.d(TAG, "onSingleTapUp: e is $e") return false } override fun onDown(event: MotionEvent): Boolean { Log.d(TAG, "onDown: $event") return true } override fun onFling( event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { Log.d(TAG, "onFling: $event1 $event2") return false } override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean { Log.d(TAG, "onScroll: distanceX is $distanceX,distanceY is $distanceY ") return false } override fun onLongPress(e: MotionEvent?) { Log.d(TAG, "onLongPress: e is $e") } }
第二步:设置双击监听
// 设置双击监听 mDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { override fun onDoubleTap(e: MotionEvent?): Boolean { Log.d(TAG, "onDoubleTap: e is e") return false } override fun onDoubleTapEvent(e: MotionEvent?): Boolean { Log.d(TAG, "onDoubleTapEvent: e is e") return false } override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { Log.d(TAG, "onSingleTapConfirmed: e is e") return false } })
最后,重写 Activity 或者 View 的 onTouchEvent ,将事件交给 mDetector 处理。
通常会有两种写法,第一种是如果手势处理器处理了,直接返回 true,进行消费。否则,进行默认处理
override fun onTouchEvent(event: MotionEvent): Boolean { return if (mDetector.onTouchEvent(event)) { true } else { super.onTouchEvent(event) } }
第二种写法是直接在 onTouchEvent 方法中,直接调用 mDetector.onTouchEvent(event) 方法
override fun onTouchEvent(event: MotionEvent): Boolean { mDetector.onTouchEvent(event) return super.onTouchEvent(event) }
第二种写法,一般不会影响当前 View 或者 Activity 事件的传递,在开发当中,有时候为了减少一些触摸事件的冲突,经常这样写。
ScaleGestureDetector 这里暂时不展开描述了了,写着写着,发现好多呀,一个周末就这样过去,贼快,觉得对你有帮助的,请来个三连,点赞,收藏,转发😆。
小结
这篇文章,其实不难。主要是将 View 的事件分发机制,滑动冲突,以及开发当中经常用到的一些知识点,总结一下。
参考博客:
ViewPager,ScrollView 嵌套ViewPager滑动冲突解决