提示:在閱讀本文章之前,請(qǐng)確保您對(duì)Touch事件的分發(fā)機(jī)制有一定的了解
在Android的學(xué)習(xí)過(guò)程中經(jīng)常會(huì)聽(tīng)到或者見(jiàn)到“回調(diào)”這個(gè)詞,那么什么是回調(diào)呢?所謂的回調(diào)函數(shù)就是:在A類中定義了一個(gè)方法,這個(gè)方法中用到了一個(gè)接口和該接口中的抽象方法,但是抽象方法沒(méi)有具體的實(shí)現(xiàn),需要B類去實(shí)現(xiàn),B類實(shí)現(xiàn)該方法后,它本身不會(huì)去調(diào)用該方法,而是傳遞給A類,供A類去調(diào)用,這種機(jī)制就稱為回調(diào)。
下面我們拿具體的Button的點(diǎn)擊事件進(jìn)行模擬分析:
首先,在View類中我們能找到setOnClickListener(OnClickListener l)方法:
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以看到,在該方法中將OnClickListener賦值給了mOnClickListener,那么我們繼續(xù)向下找,會(huì)看到在performClick()方法中執(zhí)行了我們實(shí)現(xiàn)的onClick()方法。
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()方法,但是父類卻沒(méi)有去實(shí)現(xiàn)該方法,而是定義了一個(gè)方法setOnClickListener(OnClickListener l),如果子類想要自己能夠響應(yīng)點(diǎn)擊事件,則它就必須重寫(xiě)父類的該方法,實(shí)現(xiàn)OnClickListener接口和它的onClick()方法。在子類實(shí)現(xiàn)該接口和方法后,將其通過(guò)參數(shù)傳遞給父類,在父類中執(zhí)行onClick()方法。
那么,為什么會(huì)在父類中執(zhí)行到該方法呢,這就要說(shuō)到Android中的另一個(gè)重要的機(jī)制――觸摸事件的傳遞機(jī)制。
我們知道,只要我們的手指觸摸到手機(jī)屏幕,就一定會(huì)執(zhí)行dispatchTouchEvent(MotionEvent event)方法,接下來(lái)我們就看一下dispatchTouchEvent方法中都有哪些內(nèi)容:
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;
}
這里我們不細(xì)講Touch事件的分發(fā)機(jī)制,因?yàn)榫W(wǎng)上有哥們已經(jīng)講的很清楚了。請(qǐng)參看篇首提供的鏈接。
我們看一下第17行,由于我們沒(méi)有實(shí)現(xiàn)OnTouchListener接口,而onTouch()方法的默認(rèn)返回值為false,所以第一個(gè)if語(yǔ)句中的代碼不會(huì)被執(zhí)行到,進(jìn)入第二個(gè)if語(yǔ)句中,執(zhí)行了onTouchEvent()方法。那么我們?cè)賮?lái)看一下該方法:
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;
}
代碼太長(zhǎng),我們只看重點(diǎn),在ACTION_UP這個(gè)case當(dāng)中,我們找到了關(guān)鍵的代碼(第109行):performClick()。
至此,我們已經(jīng)基本搞清楚了回調(diào)機(jī)制的整個(gè)過(guò)程。
新聞熱點(diǎn)
疑難解答
圖片精選