其實要想很好的理解安卓事件機制,最好的方式就是本身擼一個簡化的版本。也就是提取閱讀源碼後的思想。java
廢話很少說,直接開始。若是沒有閱讀安卓事件機制源碼的,能夠參考這篇安卓事件分發機制源碼詳讀 。 咱們知道事件是從 Activity 開始分發,最終回到 ViewGroup 的 dispatchTouchEvent(event) 。這裏本質上就是模仿,新建一個安卓工程,並添加一個 java 庫的 lib, 注意是 java lib。git
爲了提取思想,咱們也是仿造安卓事件機制部分源碼。這裏也新建一個 MyViewGroup 的 java 類繼承自 MyView, 由於事件機制中 ViewGroup 也是繼承自 View,這裏也是作了類似的操做。咱們在使用手勢的時候,應該對 MotionEvent 確定是不陌生的,這個類中封裝了當前點相對於控件的 x,y 座標,以及當前的事件類型,好比 ACTION_DOWN、ACTION_MOVE、ACTION_UP。 固然這裏只是簡化了的。github
首先闡述核心原理,在 MyViewGroup 中保存添加的子視圖,當一個事件來的時候先來到 MyViewGroup, 而後倒敘遍歷查找容器的的子視圖是否有被符合點擊範圍的,若是符合。則將事件分發給這個子視圖,子視圖若是是容器,則會繼續遍歷其子視圖。若是是子視圖就檢查是否攔截事件,若是不攔截就會返回到父視圖的 onTouchEvent,父視圖能夠選擇攔截,反之繼續往上層容器返回事件。bash
先來看 MyViewGroup 的 dispatchTouchEvent(event)ide
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
System.out.println("當前視圖容器的名稱: " + super.name);
int actionMasked = event.getActionMasked();
boolean intercepted = false;
boolean handled = false;
// 當按下事件來
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 容器視圖能夠重寫此方法返回 true 攔截事件
intercepted = onInterceptTouchEvent(event);
}
boolean alreadyDispatchedToNewTouchTarget = false;
// 事件未被攔截也沒有取消
if (!intercepted && actionMasked != MotionEvent.ACTION_CANCEL) {
// 獲取按下的 x, y 距離視圖的左上點
float x = event.getX();
float y = event.getY();
int childrenCount = mChildrenCount;
MyView[] children = mChildren;
// 採起倒敘遍歷的方式,由於通常日後添加的視圖都會在最上面可見。
for (int i = childrenCount - 1; i >= 0; i--) {
MyView child = children[i];
// 檢查按下的座標是否在 view 上.
if (!isTransformedTouchPointInView(x, y, child)) {
System.out.println("未點擊的view上: " + child.name);
continue;
}
// 將事件交給 child 來處理,由 child 決定是否消費
TouchTarget newTouchTarget;
if (dispatchTransformedTouchEvent(event, child)) {
newTouchTarget = addTouchTarget(child);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
// 若是沒有事件被消費就會將事件交給容器來處理.
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(event, null);
} else {
if (alreadyDispatchedToNewTouchTarget) {
handled = true;
}
}
return handled;
}
複製代碼
代碼中有詳細的註釋, 接着來看 dispatchTransformedTouchEvent(event), 這個方法就很簡單了,就是根據 child 判斷,是否繼續傳遞,仍是將事件返回給父視圖post
private boolean dispatchTransformedTouchEvent(MotionEvent event, MyView child) {
boolean handled = false;
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
複製代碼
怎麼檢查手勢是否在按下的範圍呢?isTransformedTouchPointInView(float x, float y, MyView child), 其實也很簡單,也就是 x 的起點是否大於左邊 mLeft且小於右側 mRight, y 軸大於頂部 mTop, 小於底部 mBottom。ui
private boolean isTransformedTouchPointInView(float x, float y, MyView child) {
if (x >= child.mLeft && x <= child.mRight && y >= child.mTop && y <= child.mBottom) {
return true;
}
return false;
}
複製代碼
接下來經過調試來驗證代碼,首先沒有任何視圖消費事件,看看是否是符合安卓事件機制的分發過程。當運行後,看到以下打印,能夠看到若是視圖不消費事件就會將事件回傳。並調用 視圖 onTouchEvent。spa
第一個驗證符合,接下來咱們讓容器攔截掉事件。看看還會不會往下傳遞, onInterceptTouchEvent 返回true。調試
// container 容器返回 true
public boolean onInterceptTouchEvent(MotionEvent event) {
return true;
}
複製代碼
能夠看到只有 container 容器的 onTouchEvent 被調用。第二個驗證符合。 code
第三個驗證來看,子視圖若是設置了 setOntouchListener 後還會不會調用 onTouchEvent
myView.setOnTouchListener(new MyView.OnTouchListener() {
@Override
public boolean onTouch(MotionEvent event) {
System.out.println("myView 視圖的 onTouch 方法被調用.");
return true;
}
});
複製代碼
能夠看到沒有打印任何相關 onTouchEvent 的信息, 咱們返回 false 看看。
myView.setOnTouchListener(new MyView.OnTouchListener() {
@Override
public boolean onTouch(MotionEvent event) {
System.out.println("myView 視圖的 onTouch 方法被調用.");
return false;
}
});
複製代碼
是否是就先打印了 onTouch, 而後再打印了 onTouchEvent。 因爲沒有事件被消費,因此事件最終會被回傳到 container 容器。
驗證最後一個,就是點擊事件確定是在觸摸事件以後被調用,前提是 onTouch 沒有攔截。
myView.setOnTouchListener(new MyView.OnTouchListener() {
@Override
public boolean onTouch(MotionEvent event) {
System.out.println("myView 視圖的 onTouch 方法被調用.");
return false;
}
});
myView.setOnClickListener(new MyView.OnClickListener() {
@Override
public void onClick(MyView view) {
System.out.println("myView 視圖的 onClick 方法被調用.");
}
});
複製代碼
OK, 符合驗證。若是 onTouch 返回 true。
myView.setOnTouchListener(new MyView.OnTouchListener() {
@Override
public boolean onTouch(MotionEvent event) {
System.out.println("myView 視圖的 onTouch 方法被調用.");
return true;
}
});
myView.setOnClickListener(new MyView.OnClickListener() {
@Override
public void onClick(MyView view) {
System.out.println("myView 視圖的 onClick 方法被調用.");
}
});
複製代碼
onClick 方法就不會被調。
這就是事件機制流程,至於不少細節未去處理。好比安卓事件機制維護了一個 TouchTarget, 它本職上是一個鏈表。經過 mFirstTouchTarget 來指向它的頭節點,若是找到一個 view 須要被處理事件的時候就用頭節點去記錄它。以便後續的事件來的時候能夠很快的找處處理的視圖。
最後若是想看源碼的,能夠克隆下載查看,AndroidMotionEvent