View的事件分發機制解析


Android事件構成
在Android中,事件主要包括點按、長按、拖拽、滑動等,點按又包括單擊和雙擊,另外還包括單指操作和多指操作。所有這些都構成了Android中的事件響應。總的來說,所有的事件都由如下三個部分作為基礎:

  • 按下(ACTION_DOWN)
  • 移動(ACTION_MOVE)
  • 抬起(ACTION_UP)

所有的操作事件首先必須執行的是按下操作(ACTION_DOWN),之后所有的操作都是以按下操作作為前提,當按下操作完成后,接下來可能是一段移動(ACTION_MOVE)然后抬起(ACTION_UP),或者是按下操作執行完成后沒有移動就直接抬起。這一系列的動作在Android中都可以進行控制。

這些操作事件都發生在我們手機的觸摸屏上面,而我們手機上響應我們各種操作事件的就是各種各樣的視圖組件也就是View,在Android中,所有的視圖都繼承於View,另外通過各種布局組件(ViewGroup)來對View進行布局,ViewGroup也繼承於View。所有的UI控件例如Button、TextView都是繼承於View,而所有的布局控件例如RelativeLayout、容器控件例如ListView都是繼承於ViewGroup。所以,我們的事件操作主要就是發生在ViewViewGroup之間。

所謂點擊事件的事件分發,就是當一個MotionEvent產生了以后,系統需要把這個事件傳遞給一個具體的View(ViewGroup也繼承於View),這個傳遞的過程就叫做分發過程,這個點擊事件的分發過程需要三個很重要的方法來共同完成:disPatchTouchEvent、onInterceptTouchEvent、onTouchEvent

  • public boolean disPatchTouchEvent(MotionEvent ev)
    用來進行事件的分發,如果事件能夠傳遞給當前的View,那么此方法一定會被調用,Android中所有的點擊事件都必須經過這個方法的分發,然后決定是自身消費當前事件還是繼續往下分發給子控件處理。返回true表示不繼續分發,事件被消費了,返回false則繼續往下分發,如果是ViewGroup則分發給onInterceptTouchEvent進行判斷是否攔截該事件,這個方法的返回結果受到當前ViewonTouchEvent和下級ViewdisPatchTouchEvent方法的影響,返回結果表示是否消耗當前事件。

  • public boolean onTouchEvent(MotionEvent ev)
    diaPatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列當中,當前View無法再次接收到事件。

  • public boolean onInterceptTouchEvent(MotionEvent ev)
    是ViewGroup中才有的方法,View中沒有,它的作用是負責事件的攔截,返回true的時候表示攔截當前事件,不繼續往下分發,交給自身的onTouchEvent進行處理。返回false則不攔截,繼續往下傳。這是ViewGroup特有的方法,因為ViewGroup中可能還有子View,而在Android中View中是不能再包含子View的(IOS可以),在上述方法內部被調用,如果當前View攔截了某個事件,那么同一個事件序列中(指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,中間含有不定的ACTION_MOVE事件,最終以ACTION_UP事件結束)此方法不會被再次調用,返回結果表示是否攔截當前事件。

這三個方法可以用如下偽代碼表示:

public boolean disPathchTouchEvent(MotionEvent ev){
//consume指代點擊事件是否被消耗
boolean consume=false;
//表示當前父布局要攔截該事件
if(onInterceptTouchEvent(MotionEvent ev)){
consume=onTouchEvent(ev);
}else{
//傳遞給子元素去處理
child.disPatchTouchEvent(ev);
}
return consume;
}

為了簡單起見我們先從View的事件分發機制開始分析,然后在分析ViewGroup的,首先我們建一個簡單的項目,這個項目里只有一個Button,並且我們給這個Button設置點擊事件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>


<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:text="Click me" />


</RelativeLayout>
package com.example.testbtn;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener,OnTouchListener{

private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=(Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
btn.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
Log.d("TAG", "OnClick");
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch"+event.getAction());
return false;
}


}

界面是這樣的:
這里寫圖片描述

運行這個程序,點擊Button,查看Log打印輸出的信息:

這里寫圖片描述

(這里onTouch0代表的是ACTION_DOWN,onTouch1代表的是ACTION_UP,onTouch2表示ACTION_MOVE,因為我們只是穩穩的點擊了一下Button所以不會有ACTION_MOVE的Log信息出現)
這樣我們可以得到一個初步的結論:onTouch()方法是優先於onClick()執行的,然后我們會發現onTouch()方法有一個很明顯的和onClick()方法不同的地方的,那就是它有一個Boolean類型的返回值,如果我們把這個默認為False的返回值改為True會怎么樣呢:

這里寫圖片描述

發現了什么:onClick()方法沒有被執行,這里我們把這種現象叫做點擊事件被onTouch()消費掉了,事件不會在繼續向onClick()方法傳遞了,那么事件分發機制最基本的幾條我們已經了解了,下面我們來分析產生這種機制的根本原因。

首先我們給出一個結論:Android中所有的事件都必須經過disPatchTouchEvent(MotionEvent ev)這個方法的分發,然后決定是自身消費當前事件還是繼續往下分發給子控件處理,那么我們就來看看這個disPatchTouchEvent(MotionEvent ev)到底干了什么。

    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)) {
//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;
}

代碼有點多,我們一步步來看:

  // 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);
}

最前面這一段就是判斷當前事件是否能獲得焦點,如果不能獲得焦點或者不存在一個View那我們就直接返回False跳出循環,接下來:

  if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}

設置一些標記和處理input與手勢等傳遞,不用管,到這里:

   if (onFilterTouchEventForSecurity(event)) {
//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 (onFilterTouchEventForSecurity(event))是用來判斷View是否被遮住等,ListenerInfoView的靜態內部類,專門用來定義一些XXXListener等方法的,到了重點:

  if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

很長的一個判斷,一個個來解釋:第一個li肯定不為空,因為在這個If判斷語句之前就new了一個li,第二個條件li.mOnTouchListener != null,怎么確定這個mOnTouchListener不為空呢?我們在View類里面發現了如下方法:

 /**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/

public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}

意味着只要給控件注冊了onTouch事件這個mOnTouchListener就一定會被賦值,接下來(mViewFlags & ENABLED_MASK) == ENABLED是通過位與運算來判斷這個View是否是ENABLED的,我們默認控件都是ENABLED的所以這一條也成立,最后一條li.mOnTouchListener.onTouch(this, event)是判斷onTouch()的返回值是否為True,我們后面把默認為False的返回值改成了True,所以這一整系列的判斷都是True,那么這個disPatchTouchEvent(MotionEvent ev)方法直接就返回了True,那么接下來的代碼都不會被執行,我們下面有這么一段代碼:

 if (!result && onTouchEvent(event)) {
result = true;
}

最開始我們onTouch()方法的返回值是False的,那么

 if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

這里面的判斷就不成立,result最開始的默認值也是false,那么此時如果

onTouchEvent(event)

返回值也是True,那么if (!result && onTouchEvent(event))這個方法判斷條件成立,disPatchTouchEvent(MotionEvent ev)返回True,否則返回False。

這里我們得到兩個結論:

  • OnTouchListener的優先級比onTouchEvent要高,聯想到剛才的小Demo也可以得出onTouch方法優先於onClick()方法執行(onClick()是在onTouchEvent(event)方法中被執行的這個待會會說到)
  • 如果控件(View)的onTouch返回False或者mOnTouchListener為null(控件沒有設置setOnTouchListener方法)或者控件不是ENABLE的情況下會調用onTouchEvent方法,此時dispatchTouchEvent方法的返回值與onTouchEvent的返回值一樣。

那么接下來我們就分析dispatchTouchEvent方法里面onTouchEvent的實現,給出onTouchEvent的源碼:

 /**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/

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);
}
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;
}

代碼還是很多,我們依然一段一段來分析,最前面的一段代碼:

  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);
}

根據前面的分析我們知道這一段代碼是對當前View處於不可用狀態的情況下的分析,通過注釋我們知道即使是一個不可用狀態下的View依然會消耗點擊事件,只是不會對這個點擊事件作出響應罷了,另外通過觀察這個return返回值,只要這個ViewCLICKABLELONG_CLICKABLE或者CONTEXT_CLICKABLE有一個為True,那么返回值就是True,onTouchEvent方法會消耗當前事件。

看下一段代碼:

 if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

這段代碼的意思是如果View設置有代理,那么還會執行TouchDelegateonTouchEvent(event)方法,這個onTouchEvent(event)的工作機制看起來和OnTouchListener類似,這里不深入研究。
                     –《Android開發藝術探索》

下面看一下onTouchEvent中對點擊事件的具體處理流程:

  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);
}
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;
}

我們還是一行行來分解:

  if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
//省略
}
return false;
}

這個判斷之前描述過不再贅述,如果這個判斷不成立直接跳到方法尾部返回False,如果判斷成立則繼續進入方法內部進行一個switch(event)的判斷,這里ACTION_DOWN和ACTION_MOVE都只是進行一些必要的設置與置位,我們主要看ACTION_UP:

 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;

首先判斷了是否被按下 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;接下來判斷是不是可以獲得焦點,同時嘗試去獲取焦點:

 boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}

經過種種判斷后我們看到這一行:

 if (!post(mPerformClick)) {
performClick();
}

這是判斷如果不是longPressed則通過post在UI Thread中執行一個PerformClick的Runnable,也就是performClick方法,這個方法的源碼如下:

/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/

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;
}

我們發現了什么,那就是當ACTION_UP事件發生時,會觸發performClick()方法,如果這個View設置了OnClickListener那么最終會執行到OnClickListener的回調方法onClick(),這也就驗證了剛才所說的:onClick()方法是在onTouchEvent內部被調用的。
同我們前面找到mOnTouchListener在哪里賦值的一樣,我們也可以找到mOnClickListener在哪里賦值的:

 /**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/

public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

我們知道ViewLONG_CLICKABLE屬性默認是False的,需要的話我們可以自己在xml或者java文件中去設置,但是CLICKABLE的False與True是和具體的View有關的,比如我們知道Button是可點擊的,但是TextView默認是不可點擊的,但是如果給TextView設置了點擊事件,那么根據

 if (!isClickable()) {
setClickable(true);
}

這幾行代碼TextView也會被設置為可點擊的,同理還有setOnLongClickListener也有這種作用:

 /**
* Register a callback to be invoked when this view is clicked and held. If this view is not
* long clickable, it becomes long clickable.
*
* @param l The callback that will run
*
* @see #setLongClickable(boolean)
*/

public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}

到此,View的事件分發機制已經分析完了,整個過程查了許多資料,最主要的是跟着任玉剛老師的《Android開發藝術探索》學習,最后把這個學習的過程記錄下來就是這篇博客了,等有時間的時候把ViewGroup的事件分發機制也分析一遍。

主要參考
Android觸摸屏事件派發機制詳解與源碼分析一(View篇)
Android事件傳遞機制

請看后續 ViewGroup的事件分發機制


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com