標籤:android
先寫個簡單的demo:
布局檔案中一個繼承自ViewGroup的自訂控制項MyLayout包含一個Button:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.viewgroupdemo.MyLayout android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> </com.example.viewgroupdemo.MyLayout></RelativeLayout>
自訂控制項中重寫事件分發的兩個重要方法:onInterceptTouchEvent 和 dispatchTouchEvent
public class MyLayout extends LinearLayout {public MyLayout(Context context, AttributeSet attrs) {super(context, attrs);}//是否攔截事件的傳遞,true:攔截@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {//false:把touch事件傳遞到子控制項return false;}//LinearLayout並沒有重寫dispatchTouchEvent//ViewGroup重寫了View的dispatchTouchEvent方法@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {return super.dispatchTouchEvent(ev);}}在MainActivity中設定兩個控制項的點擊事件:
layout.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Log.i(tag, "click layout --------");}});button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Log.i(tag, "click button --------");}});
此時點擊Button,由於在外層MyLayout沒有阻止事件的傳遞,所以Button響應並處理了事件,列印"click button"的log
如果在MyLayout的onInterceptTouchEvent 中return true則表示MyLayout阻止了事件的傳遞,此時列印"click layout"
問題:當點擊螢幕時系統如何確定是哪個view被點中呢?
實際上每個view對應螢幕上的一塊矩形地區,當點擊螢幕時系統通過判斷該點屬於哪塊矩形地區來確定哪個view被選中
查看源碼解釋現象:
<pre name="code" class="java"> public boolean dispatchTouchEvent(MotionEvent ev) { ...... //在按下時處理 if (action == MotionEvent.ACTION_DOWN) { //先將view對象置為null if (mMotionTarget != null) { mMotionTarget = null; } //如果事件傳遞沒有被阻止,向內傳遞 if (disallowIntercept || !onInterceptTouchEvent(ev)) { /** * 步驟總結: * //1,找到當前控制項子控制項//2,判斷當前點中點,所在的(x,y)屬於哪個子控制項的矩形地區內//3,判斷當前子控制項是viewgroup的子類對象,還是view的子類對象 //3.1 viewgroup 上述操作再來一遍//3.2 view 嘗試讓當前view去處理這個事件(true,dispatchTouchEvent方法結束,並且返回truefalse,當前沒有傳回值) */ // reset this event's action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. final View[] children = mChildren; //擷取當前ViewGroup子節點的個數 final int count = mChildrenCount; //對當前ViewGroup子節點進行遍曆迴圈,通過判斷點中的點包含在哪個子節點確定哪個View被選中 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)) { ....... //這裡的事件分發即有可能是ViewGroup也可能是View,但最終調用的是view的dispatchTouchEvent方法 //注意:viewgroup是有重寫過view的view的dispatchTouchEvent方法 //如果為true,說明這view個子節點處理了事件,事件響應完畢,可參考下一行goole工程師的注釋~~ //如果是false,則繼續走後續到的邏輯 if (child.dispatchTouchEvent(ev)) { // Event handled, we have a target now. mMotionTarget = child; return true; } } } } } } ......省略無關代碼 // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; if (target == null) { // We don't have a target, this means we're handling the // event as a regular view. //調用當前對象父view的事件分發規則,注意當前對象為viewgroup對象,所以父view是View return super.dispatchTouchEvent(ev); }
android下ViewGroup的事件分發和處理