这篇文章不适合小白直接来阅读
原创文章,转载须要本人赞成android
我以前一直从事 Android App 开发,如今跑去作 APM 了,在公司悠闲了好一段时间,后来发现本身对 Android 本来很了解的一些东西都遗忘掉了,意识到仍是以前没有写博客致使的,因此如今想把本身回忆的一些东西记录整理下来。 写的时候参考了一些书籍和一些开源框架(主要是 ObservableScrollView),同时也动手写了一个封装了事件处理细节的框架来方便工做和学习。git
这里的事件冲突表示为整个完整的事件本应该交给父容器处理确被容器里面的 View 消费了,反过来同样。注意完整的事件说明不纯在整个事件流不纯在一会是父容器处理一会是子 view 处理的状况,这种状况后面细说,也就是说肯定是谁消耗事件了,那么后续的事件就只由他处理(若是他不处理不了,那么事件会往上抛,可是事件流仍是会通过他)github
套路框架
public boolean onInterceptTouchEvent(MotionEvent ev){ switch (ev.getActionMasked()){ case MotionEvent.ACTION_DOWN: return false; // 读者想一想这里若是返回 true 会怎么样,想不起来再往上看 case MotionEvent.ACTION_MOVE: if (事件交给我处理){ return true; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // 想一想为啥不对这两个事件进行拦截 break; } return super.onInterceptTouchEvent(ev); }
public boolean dispatchTouchEvent(MotionEvent ev){ //建议在这里处理,若是该 View 是个 ViewGroup,可能不会调用到 onTouchEvent() 方法。该方法保证能在事件传递进来后都能接收到。 switch (ev.getActionMasked()){ case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: if (处理事件){ parent.requestDisallowInterceptTouchEvent(false); } break; } return super.dispatchTouchEvent(ev); }
考虑这种状况,ViewGroup 嵌套 View, 根据手势的滑动,整个事件流,在不一样状况下回交给 ViewGroup 或者 View 处理,系统的 Android 事件分发机制表名,若是 View 的事件被拦截,那么就不会再调用 onInterceptTouchEvent 方法,后续全部事件会交给 ViewGroup 处理,这时候问题就来了,若是后续的 move 事件知足某个条件,须要交给 view 处理,那这样就无法实现了。ide
上面这种状况的解决,靠 Android 系统默认的分发机制是实现不了的,解决方案就只有本身来给 View 分发事件,当 ViewGroup 拦截事件后,后续的事件会交给 ViewGroup 的 onTouchEvent 处理, 因此咱们要在 onTouchEvent 事件里面在特定条件下向 View 手动调用 dispatchTouchEvent 方法分发事件。学习
// ViewGroup 事件处理回调 public interface LinkageListener { public boolean shouldIntercept(MotionEvent event, boolean isDown, float diffx, float diffy); public void eventDown(MotionEvent event); public void eventMove(MotionEvent event, float diffx, float diffy); public void eventCancelOrUp(MotionEvent event); } //事件分发处理类 public abstract class LinkageBaseFrameLayout extends FrameLayout{ public LinkageBaseFrameLayout(Context context) { super(context); } public LinkageBaseFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); } public LinkageBaseFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } boolean intercepting; PointF firstPoint; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (getLinkageListener() != null){ switch (ev.getActionMasked()){ case MotionEvent.ACTION_DOWN://若是拦截,那么后续事件不会传递给 子view,而且不会子啊调用这个方法 firstPoint = new PointF(ev.getX(), getY()); intercepting = getLinkageListener().shouldIntercept(ev, true, 0, 0); dispatchChildDownEvent = !intercepting; dispatchThisDownEvent = intercepting; cancelChildEvent = false; cancelThisEvent = false; firstPoint = new PointF(ev.getX(), ev.getY()); break; case MotionEvent.ACTION_MOVE: if (firstPoint == null){// 防护代码, 可能父容器是自定义的 View,好比本框架(QAQ) down 事件别拦截,其余事件被分发过来了 firstPoint = new PointF(ev.getX(), ev.getY()); } float diffx = ev.getX() - firstPoint.x; //这里 diff 采用上一次和这一次的偏移或者到起始点的偏移, 这里第二种 float diffy = ev.getY() - firstPoint.y; intercepting = getLinkageListener().shouldIntercept(ev, false, diffx, diffy); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: break; } return intercepting; } return super.onInterceptTouchEvent(ev); } boolean dispatchChildDownEvent; boolean dispatchThisDownEvent; boolean cancelChildEvent; boolean cancelThisEvent; @Override public boolean onTouchEvent(MotionEvent ev) { if (getLinkageListener() != null){ switch (ev.getActionMasked()){ case MotionEvent.ACTION_DOWN: if (intercepting){// 事件只给 viewGroup 处理 getLinkageListener().eventDown(ev); } break; case MotionEvent.ACTION_MOVE: if (firstPoint == null){// 防护代码 firstPoint = new PointF(ev.getX(), ev.getY()); } float diffx = ev.getX() - firstPoint.x;// 这里是事件开始,到当前事件坐标的偏移,固然也能够改为上一个事件到当前事件的偏移 float diffy = ev.getY() - firstPoint.y; intercepting = getLinkageListener().shouldIntercept(ev, false, diffx, diffy); if (intercepting){ // 拦截,说明接下来的事件给本 viewgrop 处理 // 1. 给前面分发给子 View 的事件建立 cancel 事件,使事件完整 if (!cancelChildEvent && dispatchChildDownEvent){ // 上一个事件是给子 View 处理,而且没有传递结束事件 MotionEvent cancelEvent = obtainMotionEvent(ev, MotionEvent.ACTION_CANCEL); dispathEvent(ev); dispathEvent(cancelEvent); cancelChildEvent = true; } dispatchChildDownEvent = false; cancelThisEvent = false; //2. 当前分配给 ViewGroup 的事件,必需要 down 事件开头(注意偏移是从 down 事件开始) if (!dispatchThisDownEvent){ dispatchThisDownEvent = true; MotionEvent downEvent = obtainMotionEvent(ev, MotionEvent.ACTION_DOWN); getLinkageListener().eventDown(downEvent); firstPoint.set(ev.getX(), ev.getY()); diffx = 0; diffy = 0; }else { getLinkageListener().eventMove(ev, diffx, diffy); } }else{// 不拦截,事件分发给子 View //1. 取消 ViewGroup 事件, 能够思考不要这段代码怎么样 if (!cancelThisEvent && dispatchThisDownEvent){ MotionEvent cancelEvent = obtainMotionEvent(ev, MotionEvent.ACTION_CANCEL); getLinkageListener().eventMove(ev, diffx, diffy); getLinkageListener().eventCancelOrUp(cancelEvent); cancelThisEvent = true; } cancelChildEvent = false; dispatchThisDownEvent = false; //2. 子 View 没有分发 down 事件,要补充 down 事件 if (!dispatchChildDownEvent){//没有分发 down 事件 dispatchChildDownEvent = true; MotionEvent downEvent = obtainMotionEvent(ev, MotionEvent.ACTION_DOWN); dispathEvent(downEvent); }else { dispathEvent(ev); } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (!cancelChildEvent && dispatchChildDownEvent){ dispathEvent(ev); cancelChildEvent = true; } if (intercepting){ if (!dispatchThisDownEvent){ dispatchThisDownEvent = true; MotionEvent downEvent = obtainMotionEvent(ev, MotionEvent.ACTION_DOWN); getLinkageListener().eventDown(downEvent); } getLinkageListener().eventCancelOrUp(ev); } break; } return true; } return super.onTouchEvent(ev); } private MotionEvent obtainMotionEvent(MotionEvent ev, int action) { MotionEvent downEvent = MotionEvent.obtain(ev); downEvent.setAction(action); return downEvent; } Rect hintRect = new Rect(); /** * 向子 View 分发事件,子 View 不必定能收到完整的事件流,所以最好手指滑动的时候,要在同一个 View 上 * * @param event */ protected void dispathEvent(MotionEvent event){ View child = null; boolean consume = false; MotionEvent temp = null; for (int i = 0; i < getChildCount(); i++){ child = getChildAt(i); child.getHitRect(hintRect); if (hintRect.contains((int)event.getX(), (int)event.getY())){ // 判断点击点的坐标是否在该 view 上 temp = MotionEvent.obtain(event); temp.offsetLocation(-hintRect.left, -hintRect.top); // event 坐标修改成相对 子 view consume |= child.dispatchTouchEvent(temp); //分发事件 if (consume) break; }; } }; /** * 本 viewgroup 处理的事件 * * @return */ public abstract LinkageListener getLinkageListener(); }
经过这种事件分发逻辑,我写了一个下拉放大的组件分享给你们GitHub:EventDispatchspa
项目效果
code