詳細(xì)介紹Android中回調(diào)函數(shù)機(jī)制
提示:在閱讀本文章之前,請(qǐng)確保您對(duì)Touch事件的分發(fā)機(jī)制有一定的了解
在Android的學(xué)習(xí)過(guò)程中經(jīng)常會(huì)聽到或者見到“回調(diào)”這個(gè)詞,那么什么是回調(diào)呢?所謂的回調(diào)函數(shù)就是:在A類中定義了一個(gè)方法,這個(gè)方法中用到了一個(gè)接口和該接口中的抽象方法,但是抽象方法沒有具體的實(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)方法:
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以看到,在該方法中將OnClickListener賦值給了mOnClickListener,那么我們繼續(xù)向下找,會(huì)看到在performClick()方法中執(zhí)行了我們實(shí)現(xiàn)的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()方法,但是父類卻沒有去實(shí)現(xiàn)該方法,而是定義了一個(gè)方法setOnClickListener(OnClickListener l),如果子類想要自己能夠響應(yīng)點(diǎn)擊事件,則它就必須重寫父類的該方法,實(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)容:
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;
}
這里我們不細(xì)講Touch事件的分發(fā)機(jī)制,因?yàn)榫W(wǎng)上有哥們已經(jīng)講的很清楚了。請(qǐng)參看篇首提供的鏈接。
我們看一下第17行,由于我們沒有實(shí)現(xiàn)OnTouchListener接口,而onTouch()方法的默認(rèn)返回值為false,所以第一個(gè)if語(yǔ)句中的代碼不會(huì)被執(zhí)行到,進(jìn)入第二個(gè)if語(yǔ)句中,執(zhí)行了onTouchEvent()方法。那么我們?cè)賮?lái)看一下該方法:
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;
}
代碼太長(zhǎng),我們只看重點(diǎn),在ACTION_UP這個(gè)case當(dāng)中,我們找到了關(guān)鍵的代碼(第109行):performClick()。
至此,我們已經(jīng)基本搞清楚了回調(diào)機(jī)制的整個(gè)過(guò)程。
- Android中回調(diào)接口的使用介紹
- Android的Fragment的生命周期各狀態(tài)和回調(diào)函數(shù)使用
- 理解Android中Activity的方法回調(diào)
- android自定義控件和自定義回調(diào)函數(shù)步驟示例
- 深入淺析Android接口回調(diào)機(jī)制
- Android觀察者模式實(shí)例分析
- android開發(fā)中使用java觀察者模式
- Android源碼學(xué)習(xí)之觀察者模式應(yīng)用及優(yōu)點(diǎn)介紹
- 詳解Android中接口回調(diào)、方法回調(diào)
- Android回調(diào)與觀察者模式的實(shí)現(xiàn)原理
相關(guān)文章
Android中對(duì)xml文件解析的3種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Android中對(duì)xml文件解析的3種方式,分別是 Dom 、 SAX 和 dom4j,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02Android App中ViewPager與Fragment結(jié)合的一些問(wèn)題解決
這篇文章主要介紹了Android App中ViewPager與Fragment結(jié)合的一些問(wèn)題解決,重點(diǎn)講解了如何更新及替換ViewPager中的Fragment,需要的朋友可以參考下2016-03-03android實(shí)現(xiàn)多點(diǎn)觸摸應(yīng)用
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)多點(diǎn)觸摸應(yīng)用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Android 在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實(shí)現(xiàn)思路
本文通過(guò)實(shí)例代碼給大家講解了Android 在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實(shí)現(xiàn)思路及解決方案,需要的朋友參考下吧2017-05-05Android編程實(shí)現(xiàn)仿心跳動(dòng)畫效果的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)仿心跳動(dòng)畫效果的方法,實(shí)例分析了Android基于線程實(shí)現(xiàn)動(dòng)畫過(guò)度效果的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Android Studio實(shí)現(xiàn)簡(jiǎn)單音樂(lè)播放功能的示例代碼
這篇文章主要介紹了Android Studio實(shí)現(xiàn)簡(jiǎn)單音樂(lè)播放功能的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Android開發(fā)之基于RecycleView實(shí)現(xiàn)的頭部懸浮控件
RecyclerView是一種類似于ListView的一個(gè)滑動(dòng)列表,但是RecyclerView和ListView相比,RecyclerView比ListView更好,這篇文章重點(diǎn)給大家介紹基于RecycleView實(shí)現(xiàn)的頭部懸浮控件,感興趣的朋友一起看看吧2019-10-10