事件傳遞在android當(dāng)中是很重要的一部分歌溉,所以也看了很多別人的講解菠剩,今天就把我自己的理解寫一下
事件傳遞涉及到的東西有
1.兩個(gè)類 View ViewGroup
2.三個(gè)方法 dispatchTouchEvent()水评,onInterceptTouchEvent(),onTouchEvent()
3.三種返回值 true,false,super()
其中View沒有onInterceptTouchEvent()方法
首先看一個(gè)經(jīng)常遇到的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimaryDark"
tools:context="com.zhoufazhan.ViewActivity">
<LinearLayout
android:id="@+id/linear"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1" />
</LinearLayout>
</LinearLayout>
LinearLayout嵌套了一個(gè)LinearLayout最里面放一個(gè)Button滚粟。此時(shí)點(diǎn)擊Button按鈕憎亚,默認(rèn)情況下會(huì)調(diào)用前面所提到的3個(gè)方法匆浙,那么是怎么調(diào)用的安寺,調(diào)用的順序是什么,請(qǐng)看下圖(圖是自己畫的首尼,不一定好看挑庶,但一定能說明問題哦)
第一眼看上去是不是有點(diǎn)眼花繚亂的,不急软能,我來給你理清楚迎捺。
首先重上往下看有三層結(jié)構(gòu): Activity,ViewGroup,View
ViewGroup對(duì)應(yīng)上面的一個(gè)LinearLayout(便于講解我這里只畫了一個(gè)ViewGroup)
View對(duì)應(yīng)Button查排,Activity為系統(tǒng)的布局凳枝,這里不做過多的說明
ViewGroup包含三個(gè)方法,Activity雹嗦,View包含兩個(gè)方法范舀。
我們假設(shè)從點(diǎn)擊開始什么都不做(也就是所有的返回值都是super),從流程圖可以看出依次調(diào)用了罪,Activity锭环,ViewGroup,View的dispatchTouchEvent()方法泊藕,然后在返回依次調(diào)用View,ViewGroup,Activity的onTouchEvent()方法辅辩,這就是人們經(jīng)常說的U型圖。
下面再來詳細(xì)的說一下各個(gè)方法有什么作用。
這里我們只討論ViewGroup和View玫锋,也是我們經(jīng)常用的蛾茉,以及Action_down事件
dispatchTouchEvent()字面就能理解用處是事件的分發(fā),所有View的點(diǎn)擊或是滑動(dòng)首先調(diào)用這個(gè)方法撩鹿,具體是怎么分發(fā)的由它的返回值確定
當(dāng)返回true時(shí)谦炬,事件到此結(jié)束,它自己消費(fèi)掉了节沦,那么button按鈕將不會(huì)有任何反應(yīng)
當(dāng)返回false時(shí)键思,意思就是說告訴以后的事件,到我這里不在分發(fā)了甫贯,直接返回到上一級(jí)吼鳞,調(diào)用上級(jí)的
onTouchEvent,自己不消費(fèi)也不分發(fā)給下面的(站著茅坑..........),所以點(diǎn)擊button不會(huì)有任何反應(yīng)
那么我點(diǎn)擊button為什么會(huì)有反應(yīng)呢叫搁,因?yàn)閂iewGroup默認(rèn)會(huì)調(diào)用super()方法赔桌,注意當(dāng)ViewGroup調(diào)用super后還會(huì)調(diào)用自己的onInterceptTouchEvent(),詢問一下自己是否需要攔截事件,當(dāng)然默認(rèn)是不會(huì)攔截的也就是返回super或是false渴逻,要是你就不想把事件傳遞給button就返回true疾党,告訴ViewGroup攔截事件,然后就會(huì)調(diào)用自己的onTouchEvent
假設(shè)沒有攔截裸卫,就會(huì)來到我們期待已久的View(Button),button都等急死了仿贬,經(jīng)過了這么多攔截終于到自己了。
這時(shí)View也會(huì)和ViewGroup一樣只是沒有了攔截(onInterceptTouchEvent墓贿,因?yàn)樽约壕褪亲詈笠粚恿思肜幔劜簧蠑r截誰,只有被別人攔截的份聋袋,可憐岸游啊)
到此dispatchTouchEvent方法傳遞到底了,該輪到onTouchEvent從底部向上傳遞(如果順利的話)
圖中我們可以清晰的看到onTouchEvent返回true的時(shí)候事件就被消費(fèi)了幽勒。
那么問題來了當(dāng)我們給Button設(shè)置點(diǎn)擊事件的時(shí)候View是怎么處理的嗜侮,來看看源碼一探究竟。
首先調(diào)用的是Button的dispatchTouchEvent啥容,Button的父類TextView木有這個(gè)方法锈颗,在向上找找看,終于在View里面找到了咪惠,代碼如下击吱,不是很長(zhǎng)
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//----------------------------------------------------------------------------------------------------
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
//----------------------------------------------------------------------------------------------------
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
里面定義了一個(gè)變量result,我們只關(guān)心有返回值的代碼遥昧,這里我用虛線框起來了覆醇,請(qǐng)注意虛線里面的第一個(gè)if判斷里面的mOnTouchListener朵纷,這里就是我們給Button設(shè)置的
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
在第二個(gè)if判斷里面會(huì)調(diào)用自己的onTouchEvent,(跟我們之前分析的一樣),到這里你就知道了onTouch和onTouchEvent誰先調(diào)用了吧永脓,如果onTouch返回true將不會(huì)再調(diào)用onTouchEvent袍辞,如果我們沒有設(shè)置button.setOnTouchListener而是只設(shè)置了button.setOnClickListener,將會(huì)調(diào)用onTouchEvent常摧,再來看看里面做了什么
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
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, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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();
}
mIgnoreNextUpEvent = false;
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();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// 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;
}
代碼有點(diǎn)多搅吁,我們只看看虛線下面兩行,里面有PerformClick排宰,點(diǎn)進(jìn)去看看
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
看到mOnClickListener了嗎似芝,這就是我們?cè)O(shè)置的點(diǎn)擊事件那婉,在這里返回true板甘,也就是onTouchEvent返回true,點(diǎn)擊事件被消費(fèi)了详炬,好了就說這么多了盐类。