Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android事件分發詳解(一)——View的事件分發

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

編輯:關於Android編程

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;
	@Override
	protected 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() {
			@Override
			public 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() {
			@Override
			public void onClick(View v) {
				System.out.println("Button Clicked");
			}
		});
		
	}
	
	private void initImageView(){
		mImageView=(ImageView) findViewById(R.id.imageView);
		mImageView.setOnTouchListener(new OnTouchListener() {
			@Override
			public 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_UP
		mImageView.setOnClickListener(new OnClickListener() {
			@Override
			public 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
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		return super.onTouchEvent(event);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		return super.dispatchTouchEvent(event);
	}
	
	

}

main.xml如下:


    



PS:

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

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

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

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

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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved