成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

View事件機(jī)制分析

bergwhite / 3495人閱讀

摘要:注意,事件分發(fā)是向下傳遞的,也就是父到子的順序。事件分發(fā)機(jī)制的本質(zhì)是要解決,點(diǎn)擊事件由哪個(gè)對(duì)象發(fā)出,經(jīng)過(guò)哪些對(duì)象,最終達(dá)到哪個(gè)對(duì)象并最終得到處理。表示以及分發(fā)給其中在內(nèi)部完成被賦值。會(huì)自己處理事件。

目錄介紹

01.Android中事件分發(fā)順序

1.1 事件分發(fā)的對(duì)象是誰(shuí)

1.2 事件分發(fā)的本質(zhì)

1.3 事件在哪些對(duì)象間進(jìn)行傳遞

1.4 事件分發(fā)過(guò)程涉及方法

1.5 Android中事件分發(fā)順序

02.Activity的事件分發(fā)機(jī)制

2.1 源碼分析

2.2 點(diǎn)擊事件調(diào)用順序

2.3 得出結(jié)論

03.ViewGroup事件的分發(fā)機(jī)制

3.1 看一下這個(gè)案例

3.2 源碼分析

3.3 得出結(jié)論

04.View事件的分發(fā)機(jī)制

4.1 源碼分析

4.2 得出結(jié)論

4.3 驗(yàn)證結(jié)論

05.思考一下

5.1 onTouch()和onTouchEvent()的區(qū)別

5.2 Touch事件的后續(xù)事件傳遞

好消息

博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開(kāi)發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開(kāi)源的文件是markdown格式的!同時(shí)也開(kāi)源了生活博客,從12年起,積累共計(jì)N篇[近100萬(wàn)字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請(qǐng)注明出處,謝謝!

鏈接地址:https://github.com/yangchong2...

如果覺(jué)得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬(wàn)事起于忽微,量變引起質(zhì)變!

01.Android中事件分發(fā)順序 1.1 事件分發(fā)的對(duì)象是誰(shuí)

事件分發(fā)的對(duì)象是事件。注意,事件分發(fā)是向下傳遞的,也就是父到子的順序。

當(dāng)用戶觸摸屏幕時(shí)(View或ViewGroup派生的控件),將產(chǎn)生點(diǎn)擊事件(Touch事件)。

Touch事件相關(guān)細(xì)節(jié)(發(fā)生觸摸的位置、時(shí)間、歷史記錄、手勢(shì)動(dòng)作等)被封裝成MotionEvent對(duì)象

主要發(fā)生的Touch事件有如下四種:

MotionEvent.ACTION_DOWN:按下View(所有事件的開(kāi)始)

MotionEvent.ACTION_MOVE:滑動(dòng)View

MotionEvent.ACTION_CANCEL:非人為原因結(jié)束本次事件

MotionEvent.ACTION_UP:抬起View(與DOWN對(duì)應(yīng))

事件列:

從手指接觸屏幕至手指離開(kāi)屏幕,這個(gè)過(guò)程產(chǎn)生的一系列事件。即當(dāng)一個(gè)MotionEvent 產(chǎn)生后,系統(tǒng)需要把這個(gè)事件傳遞給一個(gè)具體的 View 去處理

任何事件列都是以DOWN事件開(kāi)始,UP事件結(jié)束,中間有無(wú)數(shù)的MOVE事件,如下圖:

1.2 事件分發(fā)的本質(zhì)

將點(diǎn)擊事件(MotionEvent)向某個(gè)View進(jìn)行傳遞并最終得到處理

即當(dāng)一個(gè)點(diǎn)擊事件發(fā)生后,系統(tǒng)需要將這個(gè)事件傳遞給一個(gè)具體的View去處理。這個(gè)事件傳遞的過(guò)程就是分發(fā)過(guò)程。

Android事件分發(fā)機(jī)制的本質(zhì)是要解決,點(diǎn)擊事件由哪個(gè)對(duì)象發(fā)出,經(jīng)過(guò)哪些對(duì)象,最終達(dá)到哪個(gè)對(duì)象并最終得到處理。

1.3 事件在哪些對(duì)象間進(jìn)行傳遞

Activity、ViewGroup、View

一個(gè)點(diǎn)擊事件產(chǎn)生后,傳遞順序是:Activity(Window) -> ViewGroup -> View

Android的UI界面是由Activity、ViewGroup、View及其派生類(lèi)組合而成的

View是所有UI組件的基類(lèi)

一般Button、ImageView、TextView等控件都是繼承父類(lèi)View

ViewGroup是容納UI組件的容器,即一組View的集合(包含很多子View和子VewGroup),

其本身也是從View派生的,即ViewGroup是View的子類(lèi)

是Android所有布局的父類(lèi)或間接父類(lèi):項(xiàng)目用到的布局(LinearLayout、RelativeLayout等),都繼承自ViewGroup,即屬于ViewGroup子類(lèi)。

與普通View的區(qū)別:ViewGroup實(shí)際上也是一個(gè)View,只不過(guò)比起View,它多了可以包含子View和定義布局參數(shù)的功能。

1.4 事件分發(fā)過(guò)程涉及方法

事件分發(fā)過(guò)程由這幾個(gè)方法協(xié)作完成

dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

1.5 Android中事件分發(fā)順序

Android中事件分發(fā)順序:

Activity(Window) -> ViewGroup -> View

其中:

super:調(diào)用父類(lèi)方法

true:消費(fèi)事件,即事件不繼續(xù)往下傳遞

false:不消費(fèi)事件,事件繼續(xù)往下傳遞 / 交由給父控件onTouchEvent()處理

充分理解Android分發(fā)機(jī)制,本質(zhì)上是要理解:

Activity對(duì)點(diǎn)擊事件的分發(fā)機(jī)制

ViewGroup對(duì)點(diǎn)擊事件的分發(fā)機(jī)制

View對(duì)點(diǎn)擊事件的分發(fā)機(jī)制

02.Activity的事件分發(fā)機(jī)制 2.1 源碼分析

當(dāng)一個(gè)點(diǎn)擊事件發(fā)生時(shí),事件最先傳到Activity的dispatchTouchEvent()進(jìn)行事件分發(fā)

具體是由Activity的Window來(lái)完成

我們來(lái)看下Activity的dispatchTouchEvent()的源碼

public boolean dispatchTouchEvent(MotionEvent ev) {
    //第一步
    //一般事件列開(kāi)始都是DOWN,所以這里基本是true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //第二步
        onUserInteraction();
    }
    //第三步
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

第一步

一般事件列開(kāi)始都是DOWN(按下按鈕),所以這里返回true,執(zhí)行onUserInteraction()

第二步

先來(lái)看下onUserInteraction()源碼

public void onUserInteraction() { 
}

從源碼可以看出:

該方法為空方法

從注釋得知:當(dāng)此activity在棧頂時(shí),觸屏點(diǎn)擊按home,back,menu鍵等都會(huì)觸發(fā)此方法

所以onUserInteraction()主要用于屏保

第三步

Window類(lèi)是抽象類(lèi),且PhoneWindow是Window類(lèi)的唯一實(shí)現(xiàn)類(lèi)

superDispatchTouchEvent(ev)是抽象方法

通過(guò)PhoneWindow類(lèi)中看一下superDispatchTouchEvent()的作用

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
    //mDecor是DecorView的實(shí)例
    //DecorView是視圖的頂層view,繼承自FrameLayout,是所有界面的父類(lèi)
}

接下來(lái)我們看mDecor.superDispatchTouchEvent(event):

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
//DecorView繼承自FrameLayout
//那么它的父類(lèi)就是ViewGroup
而super.dispatchTouchEvent(event)方法,其實(shí)就應(yīng)該是ViewGroup的dispatchTouchEvent()

}

得出結(jié)果

執(zhí)行g(shù)etWindow().superDispatchTouchEvent(ev)實(shí)際上是執(zhí)行了ViewGroup.dispatchTouchEvent(event)

這樣事件就從 Activity 傳遞到了 ViewGroup

2.2 點(diǎn)擊事件調(diào)用順序

三個(gè)方法執(zhí)行順序

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    LogUtils.e("yc----------事件攔截----------");
    return super.onInterceptTouchEvent(e);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    LogUtils.e("yc----------事件分發(fā)----------");
    return super.dispatchTouchEvent(ev);
}

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent e) {
    LogUtils.e("yc----------事件觸摸----------");
    return super.onTouchEvent(e);
}

執(zhí)行結(jié)果如下

yc----------事件分發(fā)----------
yc----------事件攔截----------
yc----------事件觸摸----------

下面將用一段偽代碼來(lái)闡述上述三個(gè)方法的關(guān)系和點(diǎn)擊事件傳遞規(guī)則

// 點(diǎn)擊事件產(chǎn)生后,會(huì)直接調(diào)用dispatchTouchEvent分發(fā)方法
public boolean dispatchTouchEvent(MotionEvent ev) {
    //代表是否消耗事件
    boolean consume = false;

    if (onInterceptTouchEvent(ev)) {
        //如果onInterceptTouchEvent()返回true則代表當(dāng)前View攔截了點(diǎn)擊事件
        //則該點(diǎn)擊事件則會(huì)交給當(dāng)前View進(jìn)行處理
        //即調(diào)用onTouchEvent ()方法去處理點(diǎn)擊事件
        consume = onTouchEvent (ev) ;
    } else {
        //如果onInterceptTouchEvent()返回false則代表當(dāng)前View不攔截點(diǎn)擊事件
        //則該點(diǎn)擊事件則會(huì)繼續(xù)傳遞給它的子元素
        //子元素的dispatchTouchEvent()就會(huì)被調(diào)用,重復(fù)上述過(guò)程
        //直到點(diǎn)擊事件被最終處理為止
        consume = child.dispatchTouchEvent (ev) ;
    }
    return consume;
}

當(dāng)一個(gè)點(diǎn)擊事件發(fā)生時(shí),調(diào)用順序如下

1.事件最先傳到Activity的dispatchTouchEvent()進(jìn)行事件分發(fā)

2.調(diào)用Window類(lèi)實(shí)現(xiàn)類(lèi)PhoneWindow的superDispatchTouchEvent()

3.調(diào)用DecorView的superDispatchTouchEvent()

4.最終調(diào)用DecorView父類(lèi)的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()

2.3 得出結(jié)論

當(dāng)一個(gè)點(diǎn)擊事件發(fā)生時(shí),事件最先傳到Activity的dispatchTouchEvent()進(jìn)行事件分發(fā),最終是調(diào)用了ViewGroup的dispatchTouchEvent()方法

這樣事件就從 Activity 傳遞到了 ViewGroup

03.ViewGroup事件的分發(fā)機(jī)制 3.1 看一下這個(gè)案例

布局如下:

結(jié)果測(cè)試

只點(diǎn)擊Button,發(fā)現(xiàn)執(zhí)行順序:btn1,btn2

再點(diǎn)擊空白處,發(fā)現(xiàn)執(zhí)行順序:btn1,btn2,viewGroup

從上面的測(cè)試結(jié)果發(fā)現(xiàn):

當(dāng)點(diǎn)擊Button時(shí),執(zhí)行Button的onClick(),但ViewGroupLayout注冊(cè)的onTouch()不會(huì)執(zhí)行

只有點(diǎn)擊空白區(qū)域時(shí)才會(huì)執(zhí)行ViewGroupLayout的onTouch();

結(jié)論:Button的onClick()將事件消費(fèi)掉了,因此事件不會(huì)再繼續(xù)向下傳遞。

3.2 源碼分析

ViewGroup的dispatchTouchEvent()源碼分析,該方法比較復(fù)雜,截取幾個(gè)重要的邏輯片段進(jìn)行介紹,來(lái)解析整個(gè)分發(fā)流程。

// 發(fā)生ACTION_DOWN事件或者已經(jīng)發(fā)生過(guò)ACTION_DOWN,并且將mFirstTouchTarget賦值,才進(jìn)入此區(qū)域,主要功能是攔截器
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
    //disallowIntercept:是否禁用事件攔截的功能(默認(rèn)是false),即不禁用
    //可以在子View通過(guò)調(diào)用requestDisallowInterceptTouchEvent方法對(duì)這個(gè)值進(jìn)行修改,不讓該View攔截事件
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    //默認(rèn)情況下會(huì)進(jìn)入該方法
    if (!disallowIntercept) {
        //調(diào)用攔截方法
        intercepted = onInterceptTouchEvent(ev); 
        ev.setAction(action);
    } else {
        intercepted = false;
    }
} else {
    // 當(dāng)沒(méi)有觸摸targets,且不是down事件時(shí),開(kāi)始持續(xù)攔截觸摸。
    intercepted = true;
}

這一段的內(nèi)容主要是為判斷是否攔截。如果當(dāng)前事件的MotionEvent.ACTION_DOWN,則進(jìn)入判斷,調(diào)用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果mFirstTouchTarget != null,即已經(jīng)發(fā)生過(guò)MotionEvent.ACTION_DOWN,并且該事件已經(jīng)有ViewGroup的子View進(jìn)行處理了,那么也進(jìn)入判斷,調(diào)用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果不是以上兩種情況,即已經(jīng)是MOVE或UP事件了,并且之前的事件沒(méi)有對(duì)象進(jìn)行處理,則設(shè)置成true,開(kāi)始攔截接下來(lái)的所有事件。這也就解釋了如果子View的onTouchEvent()方法返回false,那么接下來(lái)的一些列事件都不會(huì)交給他處理。如果VieGroup的onInterceptTouchEvent()第一次執(zhí)行為true,則mFirstTouchTarget = null,則也會(huì)使得接下來(lái)不會(huì)調(diào)用onInterceptTouchEvent(),直接將攔截設(shè)置為true。

當(dāng)ViewGroup不攔截事件的時(shí)候,事件會(huì)向下分發(fā)交由它的子View或ViewGroup進(jìn)行處理。

  /* 從最底層的父視圖開(kāi)始遍歷,
   ** 找尋newTouchTarget,即上面的mFirstTouchTarget
   ** 如果已經(jīng)存在找尋newTouchTarget,說(shuō)明正在接收觸摸事件,則跳出循環(huán)。
    */
for (int i = childrenCount - 1; i >= 0; i--) {
  final int childIndex = customOrder
    ? getChildDrawingOrder(childrenCount, i) : i;
  final View child = (preorderedList == null)
    ? children[childIndex] : preorderedList.get(childIndex);

  // 如果當(dāng)前視圖無(wú)法獲取用戶焦點(diǎn),則跳過(guò)本次循環(huán)
  if (childWithAccessibilityFocus != null) {
     if (childWithAccessibilityFocus != child) {
        continue;
     }
     childWithAccessibilityFocus = null;
     i = childrenCount - 1;
  }
  //如果view不可見(jiàn),或者觸摸的坐標(biāo)點(diǎn)不在view的范圍內(nèi),則跳過(guò)本次循環(huán)
  if (!canViewReceivePointerEvents(child) 
      || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
    }

   newTouchTarget = getTouchTarget(child);
   // 已經(jīng)開(kāi)始接收觸摸事件,并退出整個(gè)循環(huán)。
   if (newTouchTarget != null) {
       newTouchTarget.pointerIdBits |= idBitsToAssign;
       break;
    }

    //重置取消或抬起標(biāo)志位
    //如果觸摸位置在child的區(qū)域內(nèi),則把事件分發(fā)給子View或ViewGroup
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // 獲取TouchDown的時(shí)間點(diǎn)
        mLastTouchDownTime = ev.getDownTime();
        // 獲取TouchDown的Index
        if (preorderedList != null) {
           for (int j = 0; j < childrenCount; j++) {
               if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                }
           }
         } else {
                 mLastTouchDownIndex = childIndex;
                }

      //獲取TouchDown的x,y坐標(biāo)
      mLastTouchDownX = ev.getX();
      mLastTouchDownY = ev.getY();
      //添加TouchTarget,則mFirstTouchTarget != null。
      newTouchTarget = addTouchTarget(child, idBitsToAssign);
      //表示以及分發(fā)給NewTouchTarget
      alreadyDispatchedToNewTouchTarget = true;
      break;
}

dispatchTransformedTouchEvent()方法實(shí)際就是調(diào)用子元素的dispatchTouchEvent()方法。

其中dispatchTransformedTouchEvent()方法的重要邏輯如下:

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} else {
    handled = child.dispatchTouchEvent(event);
}

由于其中傳遞的child不為空,所以就會(huì)調(diào)用子元素的dispatchTouchEvent()。如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就會(huì)被賦值,同時(shí)跳出for循環(huán)。

//添加TouchTarget,則mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
 //表示以及分發(fā)給NewTouchTarget
 alreadyDispatchedToNewTouchTarget = true;

其中在addTouchTarget(child, idBitsToAssign);內(nèi)部完成mFirstTouchTarget被賦值。如果mFirstTouchTarget為空,將會(huì)讓ViewGroup默認(rèn)攔截所有操作。如果遍歷所有子View或ViewGroup,都沒(méi)有消費(fèi)事件。ViewGroup會(huì)自己處理事件。

3.3 得出結(jié)論

Android事件分發(fā)是先傳遞到ViewGroup,再由ViewGroup傳遞到View

在ViewGroup中通過(guò)onInterceptTouchEvent()對(duì)事件傳遞進(jìn)行攔截

1.onInterceptTouchEvent方法返回true代表攔截事件,即不允許事件繼續(xù)向子View傳遞;

2.返回false代表不攔截事件,即允許事件繼續(xù)向子View傳遞;(默認(rèn)返回false)

3.子View中如果將傳遞的事件消費(fèi)掉,ViewGroup中將無(wú)法接收到任何事件。

04.View事件的分發(fā)機(jī)制 4.1 源碼分析

View中dispatchTouchEvent()的源碼分析

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}

從上面可以看出:

只有以下三個(gè)條件都為真,dispatchTouchEvent()才返回true;否則執(zhí)行onTouchEvent(event)方法

第一個(gè)條件:mOnTouchListener != null;
第二個(gè)條件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三個(gè)條件:mOnTouchListener.onTouch(this, event);

下面,我們來(lái)看看下這三個(gè)判斷條件:

第一個(gè)條件:mOnTouchListener!= null

//mOnTouchListener是在View類(lèi)下setOnTouchListener方法里賦值的
public void setOnTouchListener(OnTouchListener l) { 

//即只要我們給控件注冊(cè)了Touch事件,mOnTouchListener就一定被賦值(不為空)
    mOnTouchListener = l;  
}

第二個(gè)條件:(mViewFlags & ENABLED_MASK) == ENABLED

該條件是判斷當(dāng)前點(diǎn)擊的控件是否enable

由于很多View默認(rèn)是enable的,因此該條件恒定為true

第三個(gè)條件:mOnTouchListener.onTouch(this, event)

回調(diào)控件注冊(cè)Touch事件時(shí)的onTouch方法

//手動(dòng)調(diào)用設(shè)置
button.setOnTouchListener(new OnTouchListener() {  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        return false;  
    }  
});

如果在onTouch方法返回true,就會(huì)讓上述三個(gè)條件全部成立,從而整個(gè)方法直接返回true。

如果在onTouch方法里返回false,就會(huì)去執(zhí)行onTouchEvent(event)方法。

接下來(lái),我們繼續(xù)看:onTouchEvent(event)的源碼分析

public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        // 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;  
        }  
    }  
     //如果該控件是可以點(diǎn)擊的就會(huì)進(jìn)入到下兩行的switch判斷中去;

    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
    //如果當(dāng)前的事件是抬起手指,則會(huì)進(jìn)入到MotionEvent.ACTION_UP這個(gè)case當(dāng)中。

        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
               // 在經(jīng)過(guò)種種判斷之后,會(huì)執(zhí)行到關(guān)注點(diǎn)1的performClick()方法。
               //請(qǐng)往下看關(guān)注點(diǎn)1
                if ((mPrivateFlags & 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 (!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)) {  
            //關(guān)注點(diǎn)1
            //請(qǐng)往下看performClick()的源碼分析
                                performClick();  
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }  
                mPrivateFlags |= PREPRESSED;  
                mHasPerformedLongPress = false;  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                break;  
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  
                // Be lenient about moving outside of buttons  
                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
//如果該控件是可以點(diǎn)擊的,就一定會(huì)返回true
        return true;  
    }  
//如果該控件是不可以點(diǎn)擊的,就一定會(huì)返回false
    return false;  
}

關(guān)注點(diǎn)1:

performClick()的源碼分析

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  

    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}

只要mOnClickListener不為null,就會(huì)去調(diào)用onClick方法;

那么,mOnClickListener又是在哪里賦值的呢?請(qǐng)繼續(xù)看:

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

當(dāng)我們通過(guò)調(diào)用setOnClickListener方法來(lái)給控件注冊(cè)一個(gè)點(diǎn)擊事件時(shí),就會(huì)給mOnClickListener賦值(不為空),即會(huì)回調(diào)onClick()。

4.2 得出結(jié)論

1.onTouch()的執(zhí)行高于onClick()

2.每當(dāng)控件被點(diǎn)擊時(shí):

如果在回調(diào)onTouch()里返回false,就會(huì)讓dispatchTouchEvent方法返回false,那么就會(huì)執(zhí)行onTouchEvent();如果回調(diào)了setOnClickListener()來(lái)給控件注冊(cè)點(diǎn)擊事件的話,最后會(huì)在performClick()方法里回調(diào)onClick()。

onTouch()返回false(該事件沒(méi)被onTouch()消費(fèi)掉) = 執(zhí)行onTouchEvent() = 執(zhí)行OnClick()

如果在回調(diào)onTouch()里返回true,就會(huì)讓dispatchTouchEvent方法返回true,那么將不會(huì)執(zhí)行onTouchEvent(),即onClick()也不會(huì)執(zhí)行;

onTouch()返回true(該事件被onTouch()消費(fèi)掉) = dispatchTouchEvent()返回true(不會(huì)再繼續(xù)向下傳遞) = 不會(huì)執(zhí)行onTouchEvent() = 不會(huì)執(zhí)行OnClick()

4.3 驗(yàn)證結(jié)論

在回調(diào)onTouch()里返回true

TextView textView = findViewById(R.id.tv_13);
//設(shè)置OnTouchListener()
textView.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("小楊逗比","執(zhí)行了onTouch(), 動(dòng)作是:" + event.getAction());
        return true;
    }
});
//設(shè)置OnClickListener
textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d("小楊逗比","執(zhí)行了onClick()");
    }
});

打印日志如下所示

注意action為0是ACTION_DOWN,為2是ACTION_MOVE,為1是ACTION_UP。

2019-04-04 13:37:58.301 13616-13616/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:0
2019-04-04 13:37:58.315 13616-13616/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:2
2019-04-04 13:37:58.405 13616-13616/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:2
2019-04-04 13:37:58.408 13616-13616/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:1

在回調(diào)onTouch()里返回false

打印結(jié)果如下所示

2019-04-04 13:41:26.961 14006-14006/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:0
2019-04-04 13:41:26.978 14006-14006/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:2
2019-04-04 13:41:27.072 14006-14006/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:2
2019-04-04 13:41:27.074 14006-14006/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onTouch(), 動(dòng)作是:1
2019-04-04 13:41:27.076 14006-14006/org.yczbj.ycrefreshview D/小楊逗比: 執(zhí)行了onClick()

總結(jié):onTouch()返回true就認(rèn)為該事件被onTouch()消費(fèi)掉,因而不會(huì)再繼續(xù)向下傳遞,即不會(huì)執(zhí)行OnClick()。

05.思考一下 5.1 onTouch()和onTouchEvent()的區(qū)別

這兩個(gè)方法都是在View的dispatchTouchEvent中調(diào)用,但onTouch優(yōu)先于onTouchEvent執(zhí)行。

如果在onTouch方法中返回true將事件消費(fèi)掉,onTouchEvent()將不會(huì)再執(zhí)行。

特別注意:請(qǐng)看下面代碼

//&&為短路與,即如果前面條件為false,將不再往下執(zhí)行
//所以,onTouch能夠得到執(zhí)行需要兩個(gè)前提條件:
//1. mOnTouchListener的值不能為空
//2. 當(dāng)前點(diǎn)擊的控件必須是enable的。
mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
        mOnTouchListener.onTouch(this, event)

因此如果你有一個(gè)控件是非enable的,那么給它注冊(cè)onTouch事件將永遠(yuǎn)得不到執(zhí)行。對(duì)于這一類(lèi)控件,如果我們想要監(jiān)聽(tīng)它的touch事件,就必須通過(guò)在該控件中重寫(xiě)onTouchEvent方法來(lái)實(shí)現(xiàn)。

5.2 Touch事件的后續(xù)事件(MOVE、UP)層級(jí)傳遞

如果給控件注冊(cè)了Touch事件,每次點(diǎn)擊都會(huì)觸發(fā)一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)

當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時(shí)候,只有前一個(gè)事件(如ACTION_DOWN)返回true,才會(huì)收到后一個(gè)事件(ACTION_MOVE和ACTION_UP)

即如果在執(zhí)行ACTION_DOWN時(shí)返回false,后面一系列的ACTION_MOVE和ACTION_UP事件都不會(huì)執(zhí)行

從上面對(duì)事件分發(fā)機(jī)制分析知:

dispatchTouchEvent()和 onTouchEvent()消費(fèi)事件、終結(jié)事件傳遞(返回true)

而onInterceptTouchEvent 并不能消費(fèi)事件,它相當(dāng)于是一個(gè)分叉口起到分流導(dǎo)流的作用,對(duì)后續(xù)的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用

請(qǐng)記住:接收了ACTION_DOWN事件的函數(shù)不一定能收到后續(xù)事件(ACTION_MOVE、ACTION_UP)

這里給出ACTION_MOVE和ACTION_UP事件的傳遞結(jié)論

如果在某個(gè)對(duì)象(Activity、ViewGroup、View)的dispatchTouchEvent()消費(fèi)事件(返回true),那么收到ACTION_DOWN的函數(shù)也能收到ACTION_MOVE和ACTION_UP

如果在某個(gè)對(duì)象(Activity、ViewGroup、View)的onTouchEvent()消費(fèi)事件(返回true),那么ACTION_MOVE和ACTION_UP的事件從上往下傳到這個(gè)View后就不再往下傳遞了,而直接傳給自己的onTouchEvent()并結(jié)束本次事件傳遞過(guò)程。

其他介紹 01.關(guān)于博客匯總鏈接

1.技術(shù)博客匯總

2.開(kāi)源項(xiàng)目匯總

3.生活博客匯總

4.喜馬拉雅音頻匯總

5.其他匯總

02.關(guān)于我的博客

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

簡(jiǎn)書(shū):http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜馬拉雅聽(tīng)書(shū):http://www.ximalaya.com/zhubo...

開(kāi)源中國(guó):https://my.oschina.net/zbj161...

泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...

郵箱:[email protected]

segmentfault頭條:https://segmentfault.com/u/xi...

掘金:https://juejin.im/user/593943...

項(xiàng)目地址:https://github.com/yangchong2...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77658.html

相關(guān)文章

  • View事件機(jī)制源碼分析

    摘要:當(dāng)不攔截事件的時(shí)候,事件會(huì)向下分發(fā)交由它的子或進(jìn)行處理。表示以及分發(fā)給其中在內(nèi)部完成被賦值。會(huì)自己處理事件。 目錄介紹 01.Android中事件分發(fā)順序 02.Activity的事件分發(fā)機(jī)制 2.1 源碼分析 2.2 點(diǎn)擊事件調(diào)用順序 2.3 得出結(jié)論 03.ViewGroup事件的分發(fā)機(jī)制 3.1 看一下這個(gè)案例 3.2 源碼分析 3.3 得出結(jié)論 04.Vie...

    antz 評(píng)論0 收藏0
  • Android之事件分發(fā)機(jī)制

    摘要:下事件分發(fā)和消費(fèi)事件前言中與事件相關(guān)的方法包括能夠響應(yīng)的空間包括。事件分析事件分發(fā)事件發(fā)生時(shí)的方法會(huì)以隧道方式從根元素依次往下傳遞直到最內(nèi)層子元素或在中間某一元素中由于某一條件停止傳遞將事件傳遞給最外層的 Android下Touch事件分發(fā)和消費(fèi)事件 前言 Android中與touch事件相關(guān)的方法包括:dispatchTouchEvent(MotionEvent ev)、onInte...

    ningwang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<