這篇文章不適合小白直接來閱讀
原創文章,轉載須要本人贊成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