Android事件分發詳解(一)——View的事件分發

來源:互聯網
上載者:User

Android事件分發詳解(一)——View的事件分發
MainActivity如下:

package cc.cv;import android.os.Bundle;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnTouchListener;import android.widget.Button;import android.widget.ImageView;import android.app.Activity;/** * Demo描述: * View的事件分發 *  * 在View的事件分發過程中主要涉及到dispatchTouchEvent()和onTouch()以及onTouchEvent() *  * dispatchTouchEvent()返回true或者false表示是否繼續事件分發 * onTouch()返回 true或者false表示是事件是否被消耗 * onTouchEvent()中主要處理點擊Click事件 *  * 事件的分發從dispatchTouchEvent()開始. * 方法dispatchTouchEvent()傳回值為true時表示繼續事件分發;傳回值為false時表示終止事件分發. * 源碼如下: *  * public boolean dispatchTouchEvent(MotionEvent event) { *   if (mOnTouchListener!= null&&(mViewFlags & ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){ *        return true; *     } *   return onTouchEvent(event); * } *  * 該方法的傳回值有兩種情況: *  * 1 滿足if條件時,返回true.注意該if條件的三個判斷. *     1.1 mOnTouchListener不等於null *     1.2 當前控制項是enable的 *     1.3 調用mOnTouchListener.onTouch(this,event)返回的結果 *     前兩個條件沒啥可多說的,主要看看第三個條件: *     在 onTouch(View v, MotionEvent event)中會處理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP. *     該onTouch()方法返回true表示事件已經消耗,返回false表示事件未消耗. *     比如在處理ACTION_DOWN時返回true才會繼續分發ACTION_MOVE事件 *     比如在處理ACTION_MOVE時返回true才會繼續分發ACTION_UP事件 *     比如在處理ACTION_DOWN時返回false,那麼後續的ACTION_MOVE,ACTION_UP就不會再繼續分發. *     我們在代碼中也就無法捕捉到ACTION_MOVE,ACTION_UP這兩個Action了. *     * 2 假如該if條件不滿足,那麼就繼續執行,返回 onTouchEvent(event)的執行結果. *  *  * 從該dispatchTouchEvent()的源碼也可以看出 * onTouch(this,event)和 onTouchEvent(event)的區別和關係: * 1 先調用onTouch()後調用onTouchEvent() * 2 在onTouch()方法中處理了Touch事件,即處理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件 *   返回false時表示事件(每個單獨的ACTION_DOWN,ACTION_MOVE,ACTION_UP都叫一個事件,並不是說這三者聯絡在一起才是一個事件) *   未被消耗才會調用onTouchEvent(event). * 3 在onTouchEvent(event)中的ACTION_UP事件裡會調用performClick()處理OnClick點擊事件!!!! * 4 所以可知: *   4.1 Touch事件先於Click事件發生和處理,且注意onTouch()方法預設返回為false. *   4.2 只有在onTouch()返回false時(即事件未被消耗)才會調用onTouchEvent() *   4.3 在onTouchEvent()中的ACTION_UP事件會調用performClick()處理OnClick點擊事件. * 5 參見下面的onTouchEvent()源碼,請注意第三個if判斷,這個if判斷很重要!!!!!!! *   5.1 在該if條件中判斷該控制項是否是可點擊的(CLICKABLE)或者是否是可以長按的(LONG_CLICKABLE). *   5.2 如果滿足CLICKABLE和LONG_CLICKABLE中任一條件則始終會返回true給onTouchEvent()方法 *   5.3 如果CLICKABLE和LONG_CLICKABLE這兩個條件都不滿足則返回false給onTouchEvent()方法 *    *   請注意: *   Button預設情況下就是CLICKABLE和LONG_CLICKABLE的,但是ImageView在 *   預設情況下CLICKABLE和LONG_CLICKABL均為停用. *    *   所以在用Button和ImageView分別實驗OnTouchListener和OnClickListener是有區別的. *   再次提醒注意:onTouch()方法預設返回為false. *   1 Button做實驗分析dispatchTouchEvent(). *     mOnTouchListener.onTouch()返回false(預設值),所以dispatchTouchEvent() *     如上源碼中的if不滿足,於是繼續調用onTouchEvent(event)時由於Button滿足CLICKABLE和LONG_CLICKABLE *     所以最後返回給dispatchTouchEvent()的是true,即繼續事件的分發. *     所以可以捕獲到一系列的:ACTION_DOWN,ACTION_MOVE,ACTION_UP. *     這裡就解釋了為什麼在Button中雖然onTouch()返回false(預設值)但是事件分發還在繼續!!!!!!!!!!!!! *      *   2 用ImageView做實驗分析dispatchTouchEvent(). *     mOnTouchListener.onTouch()返回false(預設值),所以dispatchTouchEvent() *     如上源碼中的if不滿足,在調用onTouchEvent(event)時由於ImageView不滿足CLICKABLE和LONG_CLICKABLE *     中任何一個所以最後返回給dispatchTouchEvent()的是false,即終止事件的分發.所以對於ImageView只有 *     ACTION_DOWN沒有ACTION_MOVE和ACTION_UP *     這裡就解釋了為什麼在ImageView中在onTouch()返回裡false(預設值)就終止了事件分發!!!!!!!!!!!!! *      *   如何才可以使ImageView像Button那樣"正規的"事件分發,有如下兩個方法: *   1 為ImageView設定setOnTouchListener,且在其onTouch()方法中返回true而不是預設的false. *   2 為ImageView設定android:clickable="true"或者ImageView設定OnClickListener. *      就是說讓ImageView變得可點擊. *   3 詳情可見以下代碼中的例子,關於這一點在下面的例子中有體現. *    *    *  * 參考資料: * http://blog.csdn.net/guolin_blog/article/details/9097463 * Thank you very much *  * 代碼隨筆: * 以前也看過事件分發,也自己總結了;可理解得不夠. * 最近打算結合以前的東西和郭大嬸的部落格重新整理事件分發. * 由於理解上的差異郭大嬸的這邊部落格,我看得比較吃力;後來也和他溝通了一下. * 所以這裡是自己的理解,是正確的;只是在表述上和他的部落格有所不同. */public class MainActivity extends Activity {    private Button mButton;    private ImageView mImageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);initButton();initImageView();}private void initButton(){mButton=(Button) findViewById(R.id.button);mButton.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("Button ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("Button ACTION_MOVE");break;case MotionEvent.ACTION_UP:System.out.println("Button ACTION_UP");break;default:break;}return false;}});mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {System.out.println("Button Clicked");}});}private void initImageView(){mImageView=(ImageView) findViewById(R.id.imageView);mImageView.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("ImageView ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("ImageView ACTION_MOVE");break;case MotionEvent.ACTION_UP:System.out.println("ImageView ACTION_UP");break;default:break;}return false;}});    //因為ImageView預設是不可點擊的,所以如果屏蔽掉以下的代碼,則只有//ImageView的ACTION_DOWN沒有ACTION_MOVE和ACTION_UPmImageView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {System.out.println("ImageView Clicked");}});}}/* * 此處為onTouchEvent源碼:public boolean onTouchEvent(MotionEvent event) {    final int viewFlags = mViewFlags;    if ((viewFlags & ENABLED_MASK) == DISABLED) {        // A disabled view that is clickable still consumes the touch        // events, it just doesn't respond to them.        return (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));    }    if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }    if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {        switch (event.getAction()) {            case MotionEvent.ACTION_UP:                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {                    // take focus if we don't have it already and we should in                    // touch mode.                    boolean focusTaken = false;                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                        focusTaken = requestFocus();                    }                    if (!mHasPerformedLongPress) {                        // This is a tap, so remove the longpress check                        removeLongPressCallback();                        // Only perform take click actions if we were in the pressed state                        if (!focusTaken) {                            // Use a Runnable and post this rather than calling                            // performClick directly. This lets other visual state                            // of the view update before click actions start.                            if (mPerformClick == null) {                                mPerformClick = new PerformClick();                            }                            if (!post(mPerformClick)) {                                performClick();                            }                        }                    }                    if (mUnsetPressedState == null) {                        mUnsetPressedState = new UnsetPressedState();                    }                    if (prepressed) {                        mPrivateFlags |= PRESSED;                        refreshDrawableState();                        postDelayed(mUnsetPressedState,                                ViewConfiguration.getPressedStateDuration());                    } else if (!post(mUnsetPressedState)) {                        // If the post failed, unpress right now                        mUnsetPressedState.run();                    }                    removeTapCallback();                }                break;            case MotionEvent.ACTION_DOWN:                if (mPendingCheckForTap == null) {                    mPendingCheckForTap = new CheckForTap();                }                mPrivateFlags |= PREPRESSED;                mHasPerformedLongPress = false;                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                break;            case MotionEvent.ACTION_CANCEL:                mPrivateFlags &= ~PRESSED;                refreshDrawableState();                removeTapCallback();                break;            case MotionEvent.ACTION_MOVE:                final int x = (int) event.getX();                final int y = (int) event.getY();                // Be lenient about moving outside of buttons                int slop = mTouchSlop;                if ((x < 0 - slop) || (x >= getWidth() + slop) ||                        (y < 0 - slop) || (y >= getHeight() + slop)) {                    // Outside button                    removeTapCallback();                    if ((mPrivateFlags & PRESSED) != 0) {                        // Remove any future long press/tap checks                        removeLongPressCallback();                        // Need to switch from pressed to not pressed                        mPrivateFlags &= ~PRESSED;                        refreshDrawableState();                    }                }                break;        }        return true;    }    return false;} */



ButtonSubClass如下:

package cc.cv;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.ViewGroup;import android.widget.Button;public class ButtonSubClass extends Button {public ButtonSubClass(Context context) {super(context);// TODO Auto-generated constructor stub}public ButtonSubClass(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}public ButtonSubClass(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubreturn super.onTouchEvent(event);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {// TODO Auto-generated method stubreturn super.dispatchTouchEvent(event);}}

main.xml如下:
        



PS:

以前也整理過事件分發,不過當時太膚淺。

最近重新整理了該部分;月底發出來,算是對今年的一個告別。

其實這部分的核心就在於ViewGroup的dispatchTouchEvent()源碼部分。

這次整理,對於該部分還是沒有完全看懂;期待以後有機會繼續。

學習是一個過程,這就是體現。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.