Android——View、ViewGroup事件(Touch事件)處理機制總結

來源:互聯網
上載者:User

標籤:android   事件傳遞   

Android中的事件

Touch事件,四種狀態:

ACTION_DOWN     ——>   表示按下了螢幕,一個事件必然從ACTION_DOWN開始
ACTION_MOVE      ——>   表示移動手勢
ACTION_UP            ——>  表示離開螢幕
ACTION_CANCEL  ——>   表示取消手勢,一般由程式產生,不會由使用者產生

一個ACTION_DOWN, n個ACTION_MOVE,1個ACTION_UP,就構成了Android中眾多的事件。

Android中的事件onClick, onScroll, onFling等等,都是由許多個Touch組成的。

一個原則,所有的touch事件都是從父容器開始向下傳遞的,呈U字形。


View事件處理機制核心代碼

Android中諸如ImageView、textView、Button等控制項都沒有重寫View的dispatchTouchEvent方法,所以View的事件處理機制對這些控制項都有效。


View.java(基於android2.3.3):

    public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示該View內部消化掉了所有事件。返回false,表示View內部只處理了ACTION_DOWN事件,事件繼續傳遞,向上級View(ViewGroup)傳遞。...            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {//此處的onTouch方式就是回調的我們註冊OnTouchListener時重寫的onTouch()方法                return true;            }                if (onTouchEvent(event)) {// onTouchEvent參考下面源碼                return true;            }        ...    }


    public boolean onTouchEvent(MotionEvent event) {        ...        // 當前onTouch的組件必須是可點擊的比如Button,ImageButton等等,此處CLICKABLE為true,才會進入if方法,最後返回true。// 如果是ImageView、TexitView這些預設為不可點擊的View,此處CLICKABLE為false,最後返回false。當然會有特殊情況,如果給這些View設定了onClick監聽器,此處CLICKABLE也將為true,參考下面源碼        if (((viewFlags & CLICKABLE) == CLICKABLE ||                   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {            switch (event.getAction()) {                case MotionEvent.ACTION_UP:                    ...                                if (!post(mPerformClick)) {                                    performClick();// 實際就是回調了我們註冊的OnClickListener中重新的onClick()方法,源碼下面源碼                                }                     ...                    break;                case MotionEvent.ACTION_DOWN:                   ...                    break;                case MotionEvent.ACTION_CANCEL:                    ...                    break;                case MotionEvent.ACTION_MOVE:                   ...                    break;            }            return true;        }        return false;    }


    public void setOnClickListener(OnClickListener l) {        if (!isClickable()) {            setClickable(true);        }        getListenerInfo().mOnClickListener = l;    }


    public boolean performClick() {        ...        if (li != null && li.mOnClickListener != null) {            ...            li.mOnClickListener.onClick(this);            return true;        }        return false;    }


總結:

只有我們註冊OnTouchListener時重寫的onTouch()方法中返回false  ——> 執行onTouchEvent方法 ——>  導致onClick()回調方法執行

onTouch()方法返回true ——> onTouchEvent方法不執行 ——>  導致onClick()回調方法不會執行


ViewGroup事件處理機制核心代碼

Android中諸如LinearLayout等的五大布局控制項,都是繼承自ViewGroup,而ViewGroup本身是繼承自View,所以ViewGroup的事件處理機制對這些控制項都有效。


ViewGroup.java(基於android2.3.3):

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {...if (action == MotionEvent.ACTION_DOWN) {if (mMotionTarget != null) {mMotionTarget = null;}//onInterceptTouchEvent返回false,說明向下傳遞//onInterceptTouchEvent返回true,說明攔截if (disallowIntercept || !onInterceptTouchEvent(ev)) {...// 虛擬碼如下://1,找到當前控制項子控制項//2,判斷當前touch的點的座標(x,y)在哪個子控制項的矩形地區內//3,判斷當前子控制項是viewgroup的子類對象,還是view的子類對象//3.1  如果是viewgroup的子類: 調用其dispatchTouchEvent方法,上述操作再來一遍//3.2  view 嘗試讓當前view去處理這個事件(             true,dispatchTouchEvent方法結束,並且返回true             false,dispatchTouchEvent繼續向下執行)...}}... target = mMotionTarget//target一定是nullif (target == null) {...//調用當前viewgroup的父View的處理事件的方法return super.dispatchTouchEvent(ev);}   ...   }


    public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;// 預設返回false    }


總結:

1、dispatchTouchEvent作用:決定事件是否由onInterceptTouchEvent來攔截處理。

返回super.dispatchTouchEvent時,由onInterceptTouchEvent來決定事件的流向
返回false時,會繼續分發事件,自己內部只處理了ACTION_DOWN
返回true時,不會繼續分發事件,自己內部處理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)


2、onInterceptTouchEvent作用:攔截事件,用來決定事件是否傳向子View

返回true時,攔截後交給自己的onTouchEvent處理
返回false時,攔截後交給子View來處理


3、onTouchEvent作用:事件最終到達這個方法

返回true時,內部處理所有的事件,換句話說,後續事件將繼續傳遞給該view的onTouchEvent()處理
返回false時,事件會向上傳遞,由onToucEvent來接受,如果最上面View中的onTouchEvent也返回false的話,那麼事件就會消失


綜合案例分析

以下摘自:http://www.longdw.com/android-onintercepttouchevent-ontouchevent/

源碼:

public class MainActivity extends Activity {Group1 group1;Group2 group2;MyTextView myTv;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//--group1//----|//-------group2//---------|//------------myTvgroup1 = new Group1(this);group2 = new Group2(this);myTv = new MyTextView(this);group2.addView(myTv, new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));group1.addView(group2, new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));setContentView(group1);}}


public class Group1 extends FrameLayout {public Group1(Context context) {super(context);// TODO Auto-generated constructor stub}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubLog.d(Constant.LOGCAT, "Group1 onInterceptTouchEvent觸發事件:"+Constant.getActionTAG(ev.getAction()));return false;}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubLog.d(Constant.LOGCAT, "Group1 onTouchEvent觸發事件:"+Constant.getActionTAG(event.getAction()));return false;}}


public class Group2 extends FrameLayout {public Group2(Context context) {super(context);// TODO Auto-generated constructor stub}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubLog.d(Constant.LOGCAT, "Group2 onInterceptTouchEvent觸發事件:"+Constant.getActionTAG(ev.getAction()));return false;}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubLog.d(Constant.LOGCAT, "Group2 onTouchEvent觸發事件:"+Constant.getActionTAG(event.getAction()));return false;}}


public class MyTextView extends TextView {public MyTextView(Context context) {super(context);this.setGravity(Gravity.CENTER);this.setText("點擊我!");// TODO Auto-generated constructor stub}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubLog.d(Constant.LOGCAT, "MyTextView onTouchEvent觸發事件:"+Constant.getActionTAG(event.getAction()));return false;}}


public class Constant {public static final String LOGCAT = "logcat";public static String getActionTAG(int action) {switch (action) {case 0:return "ACTION_DOWN";case 1:return "ACTION_UP";case 2:return "ACTION_MOVE";default:return "NULL";}}}

分別重寫Group1和Group2的onInterceptTouchEvent和onTouchEvent方法,重寫MyTextView的onTouchEvent方法,最終得到的控制項階層如下:


1.在預設返回值情況下logcat輸出如下:

測試後可知預設情況下和所有方法返回值為false的結果一致,down事件的捕獲順序onInterceptTouchEvent先於onTouchEvent,由於onTouchEvent返回值為false,down事件沒被消化,後續的move和up事件沒有出現,同時逆序返回到父控制項的onTouchEvent方法來捕獲,如所示:


2.所有onTouchEvent返回值為true情況下logcat輸出如下:


輸出結果可以看出子控制項MyTextView消化了down事件,後續的move和up事件正常捕獲,由於down事件被消化,上層的onTouchEvent方法不執行,如所示:(三箭頭分別指down、move、up事件)


既然如此,如果MyTextView中onTouchEvent方法返回為false,而group1和group2的onTouchEvent方法返回true的結果自然也就如的順序了:

測試輸出結果證明了這一猜測順序:

注意:可能有人對這種情況比較疑惑,ACTION_DOWN還好理解,但是ACTION_MOVE為什麼沒有經曆myTv,而且ACTION_MOVE只經曆了group1的onInterceptTouchEvent和group2的onTouchEvent而沒有經曆group2的onInterceptTouchEvent?我開始也費解,後來想想也是,大家對比第1條,由於onTouchEvent返回了false而沒有消耗down事件導致後續的move和up都沒有出現,這裡也是一樣由於myTv中onTouchEvent返回了false也就是說沒有消耗down事件,那麼後面的move和up也都不會出在這個view裡面,但是group2截獲到了down事件,但後來的move為什麼group2中的onInterceptTouchEvent沒有執行到呢,原因大家不要忘記了onInterceptTouchEvent的初衷是什麼,返回false是讓它的子view或viewgroup類處理,而group2的子控制項顯然是myTv而myTv的onTouchEvent返回了false也就是接收不到後續的move和up事件,也就沒必要經過onInterceptTouchEvent來繼續分發了(因為分發了也還是接收不到),經過group2的onTouchEvent因為它返回的是true,截獲了事件並且消耗了事件。


3.當某個GroupView中的onInterceptTouchEvent方法返回值為true情況下logcat輸出如下(如group2):

如果在該方法返回值中返回true,那麼子控制項將擷取不到任何點擊事件,轉而向自身的onTouchEvent方法轉寄,如所示:

如果onTouchEvent方法返回值都為true,那麼根據規律結果就如順序觸發:

最後logcat的結果證實了這一猜測:


還有一篇文章也比較好,可作為這個案例的補充,http://orgcent.com/android-touch-event-mechanism/




Android——View、ViewGroup事件(Touch事件)處理機制總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.