提示:在閱讀本文章以前,請確保您對Touch事件的分發機制有必定的瞭解,若是您不瞭解請參閱:Touch事件的分發機制。java
在Android的學習過程當中常常會聽到或者見到「回調」這個詞,那麼什麼是回調呢?所謂的回調函數就是:在A類中定義了一個方法,這個方法中用到了一個接口和該接口中的抽象方法,可是抽象方法沒有具體的實現,須要B類去實現,B類實現該方法後,它自己不會去調用該方法,而是傳遞給A類,供A類去調用,這種機制就稱爲回調。ide
下面咱們拿具體的Button的點擊事件進行模擬分析:函數
首先,在View類中咱們能找到setOnClickListener(OnClickListener l)方法:post
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
能夠看到,在該方法中將OnClickListener賦值給了mOnClickListener,那麼咱們繼續向下找,會看到在performClick()方法中執行了咱們實現的onClick()方法。學習
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); return true; } return false; }
由此咱們能夠清楚的看不到,在父類中咱們要用到onClick()方法,可是父類卻沒有去實現該方法,而是定義了一個方法setOnClickListener(OnClickListener l),若是子類想要本身可以響應點擊事件,則它就必須重寫父類的該方法,實現OnClickListener接口和它的onClick()方法。在子類實現該接口和方法後,將其經過參數傳遞給父類,在父類中執行onClick()方法。this
那麼,爲何會在父類中執行到該方法呢,這就要說到Android中的另外一個重要的機制——觸摸事件的傳遞機制。spa
咱們知道,只要咱們的手指觸摸到手機屏幕,就必定會執行dispatchTouchEvent(MotionEvent event)方法,接下來咱們就看一下dispatchTouchEvent方法中都有哪些內容:.net
public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
這裏咱們不細講Touch事件的分發機制,由於網上有哥們已經講的很清楚了。請參看篇首提供的連接。code
咱們看一下第17行,因爲咱們沒有實現OnTouchListener接口,而onTouch()方法的默認返回值爲false,因此第一個if語句中的代碼不會被執行到,進入第二個if語句中,執行了onTouchEvent()方法。那麼咱們再來看一下該方法:orm
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // 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 & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_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. setPressed(true); } 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 |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); 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 & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
代碼太長,咱們只看重點,在ACTION_UP這個case當中,咱們找到了關鍵的代碼(第109行):performClick()。
至此,咱們已經基本搞清楚了回調機制的整個過程。
原創文章,能夠自由傳播,但轉載請註明出處。