編輯:關於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討論組怎麼建1、首
Android 序列化1.序列化的目的 (1).永久的保存對象數據(將對象
上一篇《仿微信底部Tab欄》中粗略的講了下底部Tab欄的封裝,不少同學在實際運用中發現了一些問題,比如我demo中的title用了actionbar,所以如果新建的Act
要研究的幾個問題 一、Behavior是什麼?為什麼要用Behavior? 二、怎麼使用Behavior? 三、從源碼角度看為什麼要這麼使用Behavior?一、Beha