編輯:關於Android編程
一直對View的事件分發機制不太明白,在項目開發中也遇到過,在網上也找到一些解決問題方法,但是其原理並不太了解,現在辭職了有時間,今天寫寫View的事件分發,結合android源碼一起來學習下,如果講的不對,往指出一起學習提高,言歸正傳。
新建一個android項目,裡面只有一個activity,有一個button,我們給Button設置setOnClickListener(),setOnTouchListener(),通過log看看結果:
btnClick.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.i(com.example.demo,button click );
}
});
btnClick.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(com.example.demo,button touch +event.getAction());
return false;
}
});
com.example.demo(30220): button touch 0 com.example.demo(30220): button touch 2 com.example.demo(30220): button touch 2 com.example.demo(30220): button touch 2 com.example.demo(30220): button touch 2 com.example.demo(30220): button touch 2 com.example.demo(30220): button touch 1 com.example.demo(30220): button click
觀察onClick和onTouch會發現onTouch()方法有返回值,默認是返回false,如果我們改為返回true,會有什麼不同,點擊打印log看看:
com.example.demo(3280): button touch 0 com.example.demo(3280): button touch 2 com.example.demo(3280): button touch 2 com.example.demo(3280): button touch 2 com.example.demo(3280): button touch 2 com.example.demo(3280): button touch 1
首先知道一點,只要你觸摸到了界面上的任何一個控件,就一定會調用該控件的dispatchTouchEvent方法。這個方法優先於onTouch和onClick先執行,當我們去點擊按鈕的時候,就會去調用Button類裡的dispatchTouchEvent方法,那我們去Button源碼中找這個方法,Button源碼很少,沒有這個方法,Button源碼如下:

雖然沒有這個方法,但我們看出Button繼承了TextView,那就到TextView中取找,但是在TextView中並沒有找到dispatchTouchEvent()方法,那就只能找TextView的父類了,而TextView的父類就是View對象了,那在View源碼中找dispatchTouchEvent()方法看看它執行邏輯:

我們首先翻譯下這個方法的說明:
@param event The motion event to be dispatched,事件動作事件派遣
@return True if the event was handled by the view, false otherwise.如果這個事件被處理了就返回true,否則會返回false,
現在看下dispatchTouchEvent()方法裡的代碼,看源碼得有個方法,不是所有的代碼都要看懂,
在dispatchTouchEvent()方法中重點是看
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
首先看第一個if語句,
mOnTouchListener變量在什麼時候初始化呢?我們追蹤下,發現它的初始化時在
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
因此這個是我們在setOntouchListener的時候,mOnTouchListener就可以賦值了,因此這個變量不會為null,
第二個條件(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控件是否是enable的,按鈕默認都是enable的,因此這個條件恆定為true
public interface OnTouchListener {
/**
* Called when a touch event is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the touch event has been dispatched to.
* @param event The MotionEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onTouch(View v, MotionEvent event);
}

而我們返回的是true,因此這個if條件判斷返回的是true,那麼就不會執行下面的語句了
if (onTouchEvent(event)) {
return true;
}
我們看看onTouchEvent(event)方法的邏輯:源碼如下:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
// 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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
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) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
mPrivateFlags |= PRESSED;
refreshDrawableState();
checkForLongClick(0);
}
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
if (!pointInView(x, y, mTouchSlop)) {
// 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;
}
我們發現這方法裡面的源碼太多了,但是onTouchEvent()方法其實我們只要看case MotionEvent.ACTION_UP裡面的代碼,因為我們手觸摸到最後倒是要執行這裡,在經過種種判斷之後,代碼會走到這裡:
if (!post(mPerformClick)) {
performClick();
}
然後執行performClick()方法,performClick方法裡面的代碼:
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
首先看if條件,mOnClickListener這個變量就是我們點擊的時候設置的,因此不會為null,然後我們看一個重要的方法,也是回調方法,
mOnClickListener.onClick(this);
這就是設置view的點擊事件,通過源碼我們現在應該明白了最初我們設置onClick和onTouch事件的傳遞順序,
總結:
1:如果view對象setOnTouchListener方法返回true,那麼view對象就不會執行click事件,如果setTouchListener設置為false,view才會執行click事件,
還有一個重要的知識,我們知道touch事件由DWON,MOVE,UP組成,如下:
btnClick.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
Log.i(com.example.demo,ACTION_DOWN);
return false;
}else if(event.getAction()==MotionEvent.ACTION_MOVE){
Log.i(com.example.demo,ACTION_MOVE);
}else{
Log.i(com.example.demo,ACTION_UP);
}
return false;
}
});
手機qq討論組怎麼建 手機qq討論組怎麼刪人
電腦版qq能夠創建討論組,那手機qq呢?答案是肯定的,手機qq討論組怎麼建?手機qq討論組怎麼刪人?下面我們就來看看相關的操作吧!手機qq討論組怎麼建1、首
Android 序列化的存儲和讀取總結及簡單使用
Android 序列化1.序列化的目的 (1).永久的保存對象數據(將對象
Android官方底部Tab欄設計規范
上一篇《仿微信底部Tab欄》中粗略的講了下底部Tab欄的封裝,不少同學在實際運用中發現了一些問題,比如我demo中的title用了actionbar,所以如果新建的Act
深入理解CoordinatorLayout.Behavior
要研究的幾個問題 一、Behavior是什麼?為什麼要用Behavior? 二、怎麼使用Behavior? 三、從源碼角度看為什麼要這麼使用Behavior?一、Beha