android 事件分發機制(ViewGroup)
昨天寫了一篇文章 是關於view 的事件分發機制 那麼今天我們將繼續上次未完成的話題,從原始碼的角度分析ViewGruop的事件分發。
首先確定下 ViewGroup 和Vew 有什麼區別 ViewGroup
顧名思義就是一個view 的集合 ,它包含很多的子View和子VewGroup,是Android中所有佈局的父類或間接父類,像LinearLayout、RelativeLayout等都是繼承自ViewGroup的。但ViewGroup實際上也是一個View,只不過比起View,它多了可以包含子View和定義佈局引數的功能。ViewGroup繼承結構示意圖如下所示:
先寫一個類繼承LinearLayout
然後寫下佈局
新增好點選事件
分別點選一下Button1、Button2和空白區域,打印出每個view
我第一個想法是 他應該先執行了buttond 觸控 再去執行 viewGroup的觸控
現在下結論還未免過早了,讓我們再來做一個實驗。
先來看一個 原始碼
public
boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
很簡單 返回 true 和false 重寫這個函式 返回true 試試看 程式碼如下
這個時候
你不管點選哪個 他觸發的都是 MyViewGroup 的觸控事件
按鈕的點選事件完全被遮蔽掉了!
之前我就說過 當用戶手指觸控的時候 肯定會觸發一個事件 就是 dispatchTouchEvent 函式
實際情況是,當你點選了某個控制元件,首先會去呼叫該控制元件所在佈局的dispatchTouchEvent方法,然後在佈局的dispatchTouchEvent方法中找到被點選的相應控制元件,再去呼叫該控制元件的dispatchTouchEvent方法。如果我們點選了MyLayout中的按鈕,會先去呼叫MyLayout的dispatchTouchEvent方法,可是你會發現MyLayout中並沒有這個方法。那就再到它的父類LinearLayout中找一找,發現也沒有這個方法。那隻好繼續再找LinearLayout的父類ViewGroup,你終於在ViewGroup中看到了這個方法,按鈕的dispatchTouchEvent方法就是在這裡呼叫的。
- publicboolean dispatchTouchEvent(MotionEvent ev) {
- finalint action = ev.getAction();
- finalfloat xf = ev.getX();
- finalfloat yf = ev.getY();
- finalfloat scrolledXFloat = xf + mScrollX;
- finalfloat scrolledYFloat = yf + mScrollY;
- final Rect frame = mTempRect;
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (action == MotionEvent.ACTION_DOWN) {
- if (mMotionTarget != null) {
- mMotionTarget = null;
- }
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- ev.setAction(MotionEvent.ACTION_DOWN);
- finalint scrolledXInt = (int) scrolledXFloat;
- finalint scrolledYInt = (int) scrolledYFloat;
- final View[] children = mChildren;
- finalint count = mChildrenCount;
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- child.getHitRect(frame);
- if (frame.contains(scrolledXInt, scrolledYInt)) {
- finalfloat xc = scrolledXFloat - child.mLeft;
- finalfloat yc = scrolledYFloat - child.mTop;
- ev.setLocation(xc, yc);
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- if (child.dispatchTouchEvent(ev)) {
- mMotionTarget = child;
- returntrue;
- }
- }
- }
- }
- }
- }
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
- if (isUpOrCancel) {
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
- final View target = mMotionTarget;
- if (target == null) {
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- returnsuper.dispatchTouchEvent(ev);
- }
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- finalfloat xc = scrolledXFloat - (float) target.mLeft;
- finalfloat yc = scrolledYFloat - (float) target.mTop;
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
- }
- mMotionTarget = null;
- returntrue;
- }
- if (isUpOrCancel) {
- mMotionTarget = null;
- }
- finalfloat xc = scrolledXFloat - (float) target.mLeft;
- finalfloat yc = scrolledYFloat - (float) target.mTop;
- ev.setLocation(xc, yc);
- if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- mMotionTarget = null;
- }
- return target.dispatchTouchEvent(ev);
- }
這個時候你就可以思考一下了,由於我們剛剛在MyLayout中重寫了onInterceptTouchEvent方法,讓這個方法返回true,導致所有按鈕的點選事件都被遮蔽了,那我們就完全有理由相信,按鈕點選事件的處理就是在第13行條件判斷的內部進行的!
另外看這一段程式碼
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- child.getHitRect(frame);
- if (frame.contains(scrolledXInt, scrolledYInt)) {
- finalfloat xc = scrolledXFloat - child.mLeft;
- finalfloat yc = scrolledYFloat - child.mTop;
- ev.setLocation(xc, yc);
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- if (child.dispatchTouchEvent(ev)) {
- mMotionTarget = child;
- returntrue;
- }
- }
- }
- }
最後再來簡單梳理一下吧。
1. Android事件分發是先傳遞到ViewGroup,再由ViewGroup傳遞到View的。
2. 在ViewGroup中可以通過onInterceptTouchEvent方法對事件傳遞進行攔截,onInterceptTouchEvent方法返回true代表不允許事件繼續向子View傳遞,返回false代表不對事件進行攔截,預設返回false。
3. 子View中如果將傳遞的事件消費掉,ViewGroup中將無法接收到任何事件。