• <kbd id="qyk40"></kbd>
  • <strike id="qyk40"></strike><samp id="qyk40"><pre id="qyk40"></pre></samp>

    轉(zhuǎn)載請(qǐng)注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315),請(qǐng)尊重他人的辛勤勞動(dòng)成果,謝謝!

    今 天這篇文章主要分析的是Android的事件分發(fā)機(jī)制,采用例子加源碼的方式讓大家深刻的理解Android事件分發(fā)的具體情況,雖然網(wǎng)上很多 Android的事件分發(fā)的文章,有些還寫(xiě)的不錯(cuò),但是我還是決定寫(xiě)這篇文章,用我自己的思維方式來(lái)幫助大家理解Android事件分發(fā),Android 事件分發(fā)到底有多重要呢?相信很多Android開(kāi)發(fā)者都明白吧,這個(gè)我就不介紹了,我也寫(xiě)了很多篇文章里面涉及到Android的事件處理的問(wèn)題,可能 不理解Android事件分發(fā)的朋友會(huì)有點(diǎn)難理解吧,不過(guò)沒(méi)關(guān)系,相信看了這篇文章的你會(huì)對(duì)Android事件分發(fā)有進(jìn)一步的理解。我這篇文章分析的源碼 是Android 2.2的源碼, 也許你會(huì)說(shuō),干嘛不去分析最新的源碼呢?我這里要解釋一下,Android 2.2的源碼跟最新的源碼在功能效果方面是一樣的,只不過(guò)最新的源碼相對(duì)于Android 2.2來(lái)說(shuō)更加健壯一些, Android 2.2的事件處理的代碼幾乎都寫(xiě)在一個(gè)方法體里面,而最新的源碼分了很多個(gè)方法寫(xiě),如果用最新的源碼調(diào)用方法會(huì)繞來(lái)繞去的,相信你看的也會(huì)暈,出于這個(gè)考 慮,我就拿Android 2.2的源碼來(lái)給大家分析。

     

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

    我 們用手指去觸摸Android手機(jī)屏幕,就會(huì)產(chǎn)生一個(gè)觸摸事件,但是這個(gè)觸摸事件在底層是怎么分發(fā)的呢?這個(gè)我還真不知道,這里涉及到操作硬件(手機(jī)屏 幕)方面的知識(shí),也就是Linux內(nèi)核方面的知識(shí),我也沒(méi)有了解過(guò)這方面的東西,所以我們可能就往上層來(lái)分析分析,我們知道Android中負(fù)責(zé)與用戶交 互,與用戶操作緊密相關(guān)的四大組件之一是Activity, 所以我們有理由相信Activity中存在分發(fā)事件的方法,這個(gè)方法就是dispatchTouchEvent(),我們先看其源碼吧

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public boolean dispatchTouchEvent(MotionEvent ev) {  

    2.   

    3.         //如果是按下?tīng)顟B(tài)就調(diào)用onUserInteraction()方法,onUserInteraction()方法  

    4.         //是個(gè)空的方法, 我們直接跳過(guò)這里看下面的實(shí)現(xiàn)  

    5.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {  

    6.             onUserInteraction();  

    7.         }  

    8.           

    9.         if (getWindow().superDispatchTouchEvent(ev)) {  

    10.             return true;  

    11.         }  

    12.           

    13.         //getWindow().superDispatchTouchEvent(ev)返回false,這個(gè)事件就交給Activity  

    14.         //來(lái)處理, Activity的onTouchEvent()方法直接返回了false  

    15.         return onTouchEvent(ev);  

    16.     }  

    這 個(gè)方法中我們還是比較關(guān)心getWindow()的superDispatchTouchEvent()方法,getWindow()返回當(dāng)前 Activity的頂層窗口Window對(duì)象,我們直接看Window API的superDispatchTouchEvent()方法

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. /**

    2.      * Used by custom windows, such as Dialog, to pass the touch screen event

    3.      * further down the view hierarchy. Application developers should

    4.      * not need to implement or call this.

    5.      *

    6.      */  

    7.     public abstract boolean superDispatchTouchEvent(MotionEvent event);  

    這個(gè)是個(gè)抽象方法,所以我們直接找到其子類來(lái)看看superDispatchTouchEvent()方法的具體邏輯實(shí)現(xiàn),Window的唯一子類是PhoneWindow,我們就看看PhoneWindow的superDispatchTouchEvent()方法

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public boolean superDispatchTouchEvent(KeyEvent event) {  

    2.         return mDecor.superDispatcTouchEvent(event);  

    3.     }  

    里 面直接調(diào)用DecorView類的superDispatchTouchEvent()方法,或許很多人不了解DecorView這個(gè) 類,DecorView是PhoneWindow的一個(gè)final的內(nèi)部類并且繼承FrameLayout的,也是Window界面的最頂層的View對(duì) 象,這是什么意思呢?別著急,我們接著往下看
    我們先新建一個(gè)項(xiàng)目,取名AndroidTouchEvent,然后直接用模擬器運(yùn)行項(xiàng)目, MainActivity的布局文件為

    [html] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  

    2.     xmlns:tools="http://schemas.android.com/tools"  

    3.     android:layout_width="match_parent"  

    4.     android:layout_height="match_parent"  

    5.     tools:context=".MainActivity" >  

    6.   

    7.     <TextView  

    8.         android:layout_width="wrap_content"  

    9.         android:layout_height="wrap_content"  

    10.         android:layout_centerHorizontal="true"  

    11.         android:layout_centerVertical="true"  

    12.         android:text="@string/hello_world" />  

    13.   

    14. RelativeLayout>  

    利用hierarchyviewer工具來(lái)查看下MainActivity的View的層次結(jié)構(gòu),如下圖



    我們看到最頂層就是PhoneWindow$DecorView,接著DecorView下面有一個(gè)LinearLayout, LinearLayout下面有兩個(gè)FrameLayout

    上 面那個(gè)FrameLayout是用來(lái)顯示標(biāo)題欄的,這個(gè)Demo中是一個(gè)TextView,當(dāng)然我們還可以定制我們的標(biāo)題欄,利用 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是我們自定義標(biāo)題欄的布局XML文件
    下面的FrameLayout是用來(lái)裝載ContentView的,也就是我們?cè)贏ctivity中 利用setContentView()方法設(shè)置的View,現(xiàn)在我們知道了,原來(lái)我們利用setContentView()設(shè)置Activity的 View的外面還嵌套了這么多的東西

    我們來(lái)理清下思路,Activity的最頂層窗體是PhoneWindow,而PhoneWindow的最頂層View是DecorView,接下來(lái)我們就看DecorView類的superDispatchTouchEvent()方法

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public boolean superDispatchTouchEvent(MotionEvent event) {  

    2.             return super.dispatchTouchEvent(event);  

    3.         }  

    在里面調(diào)用了父類FrameLayout的dispatchTouchEvent()方法,而FrameLayout中并沒(méi)有dispatchTouchEvent()方法,所以我們直接看ViewGroup的dispatchTouchEvent()方法

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. /**

    2.     * {@inheritDoc}

    3.     */  

    4.    @Override  

    5.    public boolean dispatchTouchEvent(MotionEvent ev) {  

    6.        final int action = ev.getAction();  

    7.        final float xf = ev.getX();  

    8.        final float yf = ev.getY();  

    9.        final float scrolledXFloat = xf + mScrollX;  

    10.        final float scrolledYFloat = yf + mScrollY;  

    11.        final Rect frame = mTempRect;  

    12.   

    13.        //這個(gè)值默認(rèn)是false, 然后我們可以通過(guò)requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法  

    14.        //來(lái)改變disallowIntercept的值  

    15.        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  

    16.   

    17.        //這里是ACTION_DOWN的處理邏輯  

    18.        if (action == MotionEvent.ACTION_DOWN) {  

    19.         //清除mMotionTarget, 每次ACTION_DOWN都很設(shè)置mMotionTarget為null  

    20.            if (mMotionTarget != null) {  

    21.                mMotionTarget = null;  

    22.            }  

    23.   

    24.            //disallowIntercept默認(rèn)是false, 就看ViewGroup的onInterceptTouchEvent()方法  

    25.            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  

    26.                ev.setAction(MotionEvent.ACTION_DOWN);  

    27.                final int scrolledXInt = (int) scrolledXFloat;  

    28.                final int scrolledYInt = (int) scrolledYFloat;  

    29.                final View[] children = mChildren;  

    30.                final int count = mChildrenCount;  

    31.                //遍歷其子View  

    32.                for (int i = count - 1; i >= 0; i--) {  

    33.                    final View child = children[i];  

    34.                      

    35.                    //如果該子View是VISIBLE或者該子View正在執(zhí)行動(dòng)畫(huà), 表示該View才  

    36.                    //可以接受到Touch事件  

    37.                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  

    38.                            || child.getAnimation() != null) {  

    39.                     //獲取子View的位置范圍  

    40.                        child.getHitRect(frame);  

    41.                          

    42.                        //如Touch到屏幕上的點(diǎn)在該子View上面  

    43.                        if (frame.contains(scrolledXInt, scrolledYInt)) {  

    44.                            // offset the event to the view's coordinate system  

    45.                            final float xc = scrolledXFloat - child.mLeft;  

    46.                            final float yc = scrolledYFloat - child.mTop;  

    47.                            ev.setLocation(xc, yc);  

    48.                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

    49.                              

    50.                            //調(diào)用該子View的dispatchTouchEvent()方法  

    51.                            if (child.dispatchTouchEvent(ev))  {  

    52.                                // 如果child.dispatchTouchEvent(ev)返回true表示  

    53.                             //該事件被消費(fèi)了,設(shè)置mMotionTarget為該子View  

    54.                                mMotionTarget = child;  

    55.                                //直接返回true  

    56.                                return true;  

    57.                            }  

    58.                            // The event didn't get handled, try the next view.  

    59.                            // Don't reset the event's location, it's not  

    60.                            // necessary here.  

    61.                        }  

    62.                    }  

    63.                }  

    64.            }  

    65.        }  

    66.   

    67.        //判斷是否為ACTION_UP或者ACTION_CANCEL  

    68.        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  

    69.                (action == MotionEvent.ACTION_CANCEL);  

    70.   

    71.        if (isUpOrCancel) {  

    72.            //如果是ACTION_UP或者ACTION_CANCEL, 將disallowIntercept設(shè)置為默認(rèn)的false  

    73.         //假如我們調(diào)用了requestDisallowInterceptTouchEvent()方法來(lái)設(shè)置disallowIntercept為true  

    74.         //當(dāng)我們抬起手指或者取消Touch事件的時(shí)候要將disallowIntercept重置為false  

    75.         //所以說(shuō)上面的disallowIntercept默認(rèn)在我們每次ACTION_DOWN的時(shí)候都是false  

    76.            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  

    77.        }  

    78.   

    79.        // The event wasn't an ACTION_DOWN, dispatch it to our target if  

    80.        // we have one.  

    81.        final View target = mMotionTarget;  

    82.        //mMotionTarget為null意味著沒(méi)有找到消費(fèi)Touch事件的View, 所以我們需要調(diào)用ViewGroup父類的  

    83.        //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法  

    84.        if (target == null) {  

    85.            // We don't have a target, this means we're handling the  

    86.            // event as a regular view.  

    87.            ev.setLocation(xf, yf);  

    88.            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

    89.                ev.setAction(MotionEvent.ACTION_CANCEL);  

    90.                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

    91.            }  

    92.            return super.dispatchTouchEvent(ev);  

    93.        }  

    94.   

    95.        //這個(gè)if里面的代碼ACTION_DOWN不會(huì)執(zhí)行,只有ACTION_MOVE  

    96.        //ACTION_UP才會(huì)走到這里, 假如在ACTION_MOVE或者ACTION_UP攔截的  

    97.        //Touch事件, 將ACTION_CANCEL派發(fā)給target,然后直接返回true  

    98.        //表示消費(fèi)了此Touch事件  

    99.        if (!disallowIntercept && onInterceptTouchEvent(ev)) {  

    100.            final float xc = scrolledXFloat - (float) target.mLeft;  

    101.            final float yc = scrolledYFloat - (float) target.mTop;  

    102.            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

    103.            ev.setAction(MotionEvent.ACTION_CANCEL);  

    104.            ev.setLocation(xc, yc);  

    105.              

    106.            if (!target.dispatchTouchEvent(ev)) {  

    107.            }  

    108.            // clear the target  

    109.            mMotionTarget = null;  

    110.            // Don't dispatch this event to our own view, because we already  

    111.            // saw it when intercepting; we just want to give the following  

    112.            // event to the normal onTouchEvent().  

    113.            return true;  

    114.        }  

    115.   

    116.        if (isUpOrCancel) {  

    117.            mMotionTarget = null;  

    118.        }  

    119.   

    120.        // finally offset the event to the target's coordinate system and  

    121.        // dispatch the event.  

    122.        final float xc = scrolledXFloat - (float) target.mLeft;  

    123.        final float yc = scrolledYFloat - (float) target.mTop;  

    124.        ev.setLocation(xc, yc);  

    125.   

    126.        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

    127.            ev.setAction(MotionEvent.ACTION_CANCEL);  

    128.            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

    129.            mMotionTarget = null;  

    130.        }  

    131.   

    132.        //如果沒(méi)有攔截ACTION_MOVE, ACTION_DOWN的話,直接將Touch事件派發(fā)給target  

    133.        return target.dispatchTouchEvent(ev);  

    134.    }  

    這個(gè)方法相對(duì)來(lái)說(shuō)還是蠻長(zhǎng),不過(guò)所有的邏輯都寫(xiě)在一起,看起來(lái)比較方便,接下來(lái)我們就具體來(lái)分析一下

     

    我們點(diǎn)擊屏幕上面的TextView來(lái)看看Touch是如何分發(fā)的,先看看ACTION_DOWN

    在 DecorView這一層會(huì)直接調(diào)用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都會(huì)將mMotionTarget設(shè)置為null, mMotionTarget是什么?我們先不管,繼續(xù)看代 碼,走到25行,  disallowIntercept默認(rèn)為false,我們?cè)倏碫iewGroup的onInterceptTouchEvent()方法

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public boolean onInterceptTouchEvent(MotionEvent ev) {  

    2.       return false;  

    3.   }  

    直 接返回false, 繼續(xù)往下看,循環(huán)遍歷DecorView里面的Child,從上面的MainActivity的層次結(jié)構(gòu)圖我們可以看出,DecorView里面只有一個(gè) Child那就是LinearLayout, 第43行判斷Touch的位置在不在LinnearLayout上面,這是毫無(wú)疑問(wèn)的,所以直接跳到51行, 調(diào)用LinearLayout的dispatchTouchEvent()方法,LinearLayout也沒(méi)有 dispatchTouchEvent()這個(gè)方法,所以也是調(diào)用ViewGroup的dispatchTouchEvent()方法,所以這個(gè)方法卡在 51行沒(méi)有繼續(xù)下去,而是去先執(zhí)行LinearLayout的dispatchTouchEvent()

    LinearLayout 調(diào)用dispatchTouchEvent()的邏輯跟DecorView是一樣的,所以也是遍歷LinearLayout的兩個(gè) FrameLayout,判斷Touch的是哪個(gè)FrameLayout,很明顯是下面那個(gè),調(diào)用下面那個(gè)FrameLayout的 dispatchTouchEvent(),  所以LinearLayout的dispatchTouchEvent()卡在51也沒(méi)繼續(xù)下去

    繼 續(xù)調(diào)用FrameLayout的dispatchTouchEvent()方法,和上面一樣的邏輯,下面的FrameLayout也只有一個(gè)Child, 就是RelativeLayout,F(xiàn)rameLayout的dispatchTouchEvent()繼續(xù)卡在51行,先執(zhí)行 RelativeLayout的dispatchTouchEvent()方法

    執(zhí)行RelativeLayout的 dispatchTouchEvent()方法邏輯還是一樣的,循環(huán)遍歷 RelativeLayout里面的孩子,里面只有一個(gè)TextView, 所以這里就調(diào)用TextView的dispatchTouchEvent(), TextView并沒(méi)有dispatchTouchEvent()這個(gè)方法,于是找TextView的父類View,在看View的 dispatchTouchEvent()的方法之前,我們先理清下上面這些ViewGroup執(zhí)行dispatchTouchEvent()的思路,我 畫(huà)了一張圖幫大家理清下(這里沒(méi)有畫(huà)出onInterceptTouchEvent()方法)

    上面的ViewGroup的Touch事件分發(fā)就告一段落先,因?yàn)檫@里要調(diào)用TextView(也就是View)的dispatchTouchEvent()方法,所以我們先分析View的dispatchTouchEvent()方法在將上面的繼續(xù)下去

     

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

    我們還是先看View的dispatchTouchEvent()方法的源碼

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public boolean dispatchTouchEvent(MotionEvent event) {  

    2.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  

    3.                 mOnTouchListener.onTouch(this, event)) {  

    4.             return true;  

    5.         }  

    6.         return onTouchEvent(event);  

    7.     }  

    在這個(gè)方法里面,先進(jìn)行了一個(gè)判斷

    第一個(gè)條件mOnTouchListener就是我們調(diào)用View的setTouchListener()方法設(shè)置的

    第二個(gè)條件是判斷View是否為enabled的, View一般都是enabled,除非你手動(dòng)設(shè)置為disabled

    第 三個(gè)條件就是OnTouchListener接口的onTouch()方法的返回值了,如果調(diào)用了setTouchListener()設(shè)置 OnTouchListener,并且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回 true,否則就執(zhí)行View的onTouchEvent() 并返回View的onTouchEvent()的值
    現(xiàn)在你了解了View的 onTouchEvent()方法和onTouch()的關(guān)系了吧,為什么Android提供了處理Touch事件onTouchEvent()方法還要 增加一個(gè)OnTouchListener接口呢?我覺(jué)得OnTouchListener接口是對(duì)處理Touch事件的屏蔽和擴(kuò)展作用吧,屏蔽作用我就不舉 例介紹了,看上面的源碼就知道了,我就說(shuō)下擴(kuò)展吧,比如我們要打印View的Touch的點(diǎn)的坐標(biāo),我們可以自定義一個(gè)View如下

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public class CustomView extends View {  

    2.       

    3.     public CustomView(Context context, AttributeSet attrs) {  

    4.         super(context, attrs);  

    5.     }  

    6.   

    7.     public CustomView(Context context, AttributeSet attrs, int defStyle) {  

    8.         super(context, attrs, defStyle);  

    9.     }  

    10.   

    11.     @Override  

    12.     public boolean onTouchEvent(MotionEvent event) {  

    13.           

    14.         Log.i("tag""X的坐標(biāo) = " + event.getX() + " Y的坐標(biāo) = " + event.getY());  

    15.           

    16.         return super.onTouchEvent(event);  

    17.     }  

    18.   

    19. }  

    也可以直接對(duì)View設(shè)置OnTouchListener接口,在return的時(shí)候調(diào)用下v.onTouchEvent()

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. view.setOnTouchListener(new OnTouchListener() {  

    2.               

    3.             @Override  

    4.             public boolean onTouch(View v, MotionEvent event) {  

    5.                   

    6.                 Log.i("tag""X的坐標(biāo) = " + event.getX() + " Y的坐標(biāo) = " + event.getY());  

    7.                   

    8.                 return v.onTouchEvent(event);  

    9.             }  

    10.         });  

    這樣子也實(shí)現(xiàn)了我們所需要的功能,所以我認(rèn)為OnTouchListener是對(duì)onTouchEvent()方法的一個(gè)屏蔽和擴(kuò)展作用,假如你有不一樣的理解,你也可以告訴我下,這里就不糾結(jié)這個(gè)了。

    我們?cè)倏碫iew的onTouchEvent()方法

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public boolean onTouchEvent(MotionEvent event) {  

    2.       final int viewFlags = mViewFlags;  

    3.   

    4.       if ((viewFlags & ENABLED_MASK) == DISABLED) {  

    5.           return (((viewFlags & CLICKABLE) == CLICKABLE ||  

    6.                   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  

    7.       }  

    8.   

    9.       //如果設(shè)置了Touch代理,就交給代理來(lái)處理,mTouchDelegate默認(rèn)是null  

    10.       if (mTouchDelegate != null) {  

    11.           if (mTouchDelegate.onTouchEvent(event)) {  

    12.               return true;  

    13.           }  

    14.       }  

    15.   

    16.       //如果View是clickable或者longClickable的onTouchEvent就返回true, 否則返回false  

    17.       if (((viewFlags & CLICKABLE) == CLICKABLE ||  

    18.               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

    19.           switch (event.getAction()) {  

    20.               case MotionEvent.ACTION_UP:  

    21.                   boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  

    22.                   if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  

    23.                       boolean focusTaken = false;  

    24.                       if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  

    25.                           focusTaken = requestFocus();  

    26.                       }  

    27.   

    28.                       if (!mHasPerformedLongPress) {  

    29.                           removeLongPressCallback();  

    30.   

    31.                           if (!focusTaken) {  

    32.                               if (mPerformClick == null) {  

    33.                                   mPerformClick = new PerformClick();  

    34.                               }  

    35.                               if (!post(mPerformClick)) {  

    36.                                   performClick();  

    37.                               }  

    38.                           }  

    39.                       }  

    40.   

    41.                       if (mUnsetPressedState == null) {  

    42.                           mUnsetPressedState = new UnsetPressedState();  

    43.                       }  

    44.   

    45.                       if (prepressed) {  

    46.                           mPrivateFlags |= PRESSED;  

    47.                           refreshDrawableState();  

    48.                           postDelayed(mUnsetPressedState,  

    49.                                   ViewConfiguration.getPressedStateDuration());  

    50.                       } else if (!post(mUnsetPressedState)) {  

    51.                           mUnsetPressedState.run();  

    52.                       }  

    53.                       removeTapCallback();  

    54.                   }  

    55.                   break;  

    56.   

    57.               case MotionEvent.ACTION_DOWN:  

    58.                   if (mPendingCheckForTap == null) {  

    59.                       mPendingCheckForTap = new CheckForTap();  

    60.                   }  

    61.                   mPrivateFlags |= PREPRESSED;  

    62.                   mHasPerformedLongPress = false;  

    63.                   postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  

    64.                   break;  

    65.   

    66.               case MotionEvent.ACTION_CANCEL:  

    67.                   mPrivateFlags &= ~PRESSED;  

    68.                   refreshDrawableState();  

    69.                   removeTapCallback();  

    70.                   break;  

    71.   

    72.               case MotionEvent.ACTION_MOVE:  

    73.                   final int x = (int) event.getX();  

    74.                   final int y = (int) event.getY();  

    75.   

    76.                   //當(dāng)手指在View上面滑動(dòng)超過(guò)View的邊界,  

    77.                   int slop = mTouchSlop;  

    78.                   if ((x < 0 - slop) || (x >= getWidth() + slop) ||  

    79.                           (y < 0 - slop) || (y >= getHeight() + slop)) {  

    80.                       // Outside button  

    81.                       removeTapCallback();  

    82.                       if ((mPrivateFlags & PRESSED) != 0) {  

    83.                           removeLongPressCallback();  

    84.   

    85.                           mPrivateFlags &= ~PRESSED;  

    86.                           refreshDrawableState();  

    87.                       }  

    88.                   }  

    89.                   break;  

    90.           }  

    91.           return true;  

    92.       }  

    93.   

    94.       return false;  

    95.   }  

    這 個(gè)方法也是比較長(zhǎng)的,我們先看第4行,如果一個(gè)View是disabled, 并且該View是Clickable或者longClickable, onTouchEvent()就不執(zhí)行下面的代碼邏輯直接返回true, 表示該View就一直消費(fèi)Touch事件,如果一個(gè)enabled的View,并且是clickable或者longClickable 的,onTouchEvent()會(huì)執(zhí)行下面的代碼邏輯并返回true,綜上,一個(gè)clickable或者longclickable的View是一直消 費(fèi)Touch事件的,而一般的View既不是clickable也不是longclickable的(即不會(huì)消費(fèi)Touch事件,只會(huì)執(zhí)行 ACTION_DOWN而不會(huì)執(zhí)行ACTION_MOVE和ACTION_UP) Button是clickable的,可以消費(fèi)Touch事件,但是我 們可以通過(guò)setClickable()和setLongClickable()來(lái)設(shè)置View是否為clickable和longClickable。 當(dāng)然還可以通過(guò)重寫(xiě)View的onTouchEvent()方法來(lái)控制Touch事件的消費(fèi)與否

    我們?cè)诳?7行的ACTION_DOWN, 新建一個(gè)CheckForTap,我們看看CheckForTap是什么

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. private final class CheckForTap implements Runnable {  

    2.        public void run() {  

    3.            mPrivateFlags &= ~PREPRESSED;  

    4.            mPrivateFlags |= PRESSED;  

    5.            refreshDrawableState();  

    6.            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  

    7.                postCheckForLongClick(ViewConfiguration.getTapTimeout());  

    8.            }  

    9.        }  

    10.    }  

    原 來(lái)是個(gè)Runnable對(duì)象,然后使用Handler的post方法延時(shí)ViewConfiguration.getTapTimeout()執(zhí)行 CheckForTap的run()方法,在run方法中先判斷view是否longClickable的,一般的View都是 false, postCheckForLongClick(ViewConfiguration.getTapTimeout())這段代碼就是執(zhí)行長(zhǎng) 按的邏輯的代碼,只有當(dāng)我們?cè)O(shè)置為longClickble才會(huì)去執(zhí)行 postCheckForLongClick(ViewConfiguration.getTapTimeout()),這里我就不介紹了

    由于考慮到文章篇幅的問(wèn)題,我就不繼續(xù)分析View的長(zhǎng)按事件和點(diǎn)擊事件了,在這里我直接得出結(jié)論吧

    長(zhǎng) 按事件是在ACTION_DOWN中執(zhí)行,點(diǎn)擊事件是在ACTION_UP中執(zhí)行,要想執(zhí)行長(zhǎng)按事件,這個(gè)View必須是longclickable的, 也許你會(huì)納悶,一般的View不是longClickable為什么也會(huì)執(zhí)行長(zhǎng)按事件呢?我們要執(zhí)行長(zhǎng)按事件必須要調(diào)用 setOnLongClickListener()設(shè)置OnLongClickListener接口,我們看看這個(gè)方法的源碼

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public void setOnLongClickListener(OnLongClickListener l) {  

    2.      if (!isLongClickable()) {  

    3.          setLongClickable(true);  

    4.      }  

    5.      mOnLongClickListener = l;  

    6.  }  

    看到?jīng)]有,如果這個(gè)View不是longClickable的,我們就調(diào)用setLongClickable(true)方法設(shè)置為longClickable的,所以才會(huì)去執(zhí)行長(zhǎng)按方法onLongClick();
     

    要 想執(zhí)行點(diǎn)擊事件,這個(gè)View就必須要消費(fèi)ACTION_DOWN和ACTION_MOVE事件,并且沒(méi)有設(shè)置OnLongClickListener的 情況下,如果設(shè)置了OnLongClickListener的情況下,需要onLongClick()返回false才能執(zhí)行到onClick()方法, 也許你又會(huì)納悶,一般的View默認(rèn)是不消費(fèi)touch事件的,這不是和你上面說(shuō)的相違背嘛,我們要向執(zhí)行點(diǎn)擊事件必須要調(diào)用 setOnClickListener()來(lái)設(shè)置OnClickListener接口,我們看看這個(gè)方法的源碼就知道了

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. public void setOnClickListener(OnClickListener l) {  

    2.      if (!isClickable()) {  

    3.          setClickable(true);  

    4.      }  

    5.      mOnClickListener = l;  

    6.  }  

    所以說(shuō)一個(gè)enable的并且是clickable的View是一直消費(fèi)touch事件的,所以才會(huì)執(zhí)行到onClick()方法


     

    對(duì) 于View的Touch事件的分發(fā)機(jī)制算是告一段落了,從上面我們可以得出TextView的dispatchTouchEvent()的返回false 的,即不消費(fèi)Touch事件。我們就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,由于 TextView.dispatchTouchEvent()為false, 導(dǎo)致mMotionTarget沒(méi)有被賦值,還是null, 繼續(xù)往下走執(zhí)行RelativeLayout的dispatchTouchEvent()方法, 來(lái)到第84行, 判斷target是否為null,這個(gè)target就是mMotionTarget,滿足條件,執(zhí)行92行的 super.dispatchTouchEvent(ev)代碼并返回, 這里調(diào)用的是RelativeLayout父類View的dispatchTouchEvent()方法,由于RelativeLayout沒(méi)有設(shè)置 onTouchListener, 所以這里直接調(diào)用RelativeLayout(其實(shí)就是View, 因?yàn)镽elativeLayout沒(méi)有重寫(xiě)onTouchEvent())的onTouchEvent()方法 由于RelativeLayout既不是clickable的也是longClickable的,所以其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,這里就執(zhí)行完了RelativeLayout的 dispatchTouchEvent()方法

    繼 續(xù)執(zhí)行FrameLayout的dispatchTouchEvent()的第51行,由于 RelativeLayout.dispatchTouchEvent()返回的是false, 跟上面的邏輯是一樣的, 也是執(zhí)行到92行的super.dispatchTouchEvent(ev)代碼并返回,然后執(zhí)行FrameLayout的 onTouchEvent()方法,而FrameLayout的onTouchEvent()也是返回false,所以FrameLayout的 dispatchTouchEvent()方法返回false,執(zhí)行完畢FrameLayout的dispatchTouchEvent()方法

    在上面的我就不分析了,大家自行分析一下,跟上面的邏輯是一樣的,我直接畫(huà)了個(gè)圖來(lái)幫大家理解下(這里沒(méi)有畫(huà)出onInterceptTouchEvent()方法)

    所 以我們點(diǎn)擊屏幕上面的TextView的事件分發(fā)流程是上圖那個(gè)樣子的,表示Activity的View都不消費(fèi)ACTION_DOWN事件,所以就不能 在觸發(fā)ACTION_MOVE, ACTION_UP等事件了,具體是為什么?我還不太清楚,畢竟從Activity到TextView這一層是分析不出來(lái)的,估計(jì)是在底層實(shí)現(xiàn)的。

     

    但 如果將TextView換成Button,流程是不是還是這個(gè)樣子呢?答案不是,我們來(lái)分析分析一下,如果是Button , Button是一個(gè)clickable的View,onTouchEvent()返回true, 表示他一直消費(fèi)Touch事件,所以Button的dispatchTouchEvent()方法返回true, 回到RelativeLayout的dispatchTouchEvent()方法的51行,滿足條件,進(jìn)入到if方法體,設(shè)置 mMotionTarget為Button,然后直接返回true, RelativeLayout的dispatchTouchEvent()方法執(zhí)行完畢, 不會(huì)調(diào)用到RelativeLayout的 onTouchEvent()方法

    然后到FrameLayout的dispatchTouchEvent()方法的51行,由于 RelativeLayout.dispatchTouchEvent()返回true, 滿足條件,進(jìn)入if方法體,設(shè)置mMotionTarget為RelativeLayout,注意下,這里的mMotionTarget跟 RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一個(gè)哦,因?yàn)樗麄兪遣煌姆椒ㄖ械模?后返回true

    同理FrameLayout的dispatchTouchEvent()也是返回true, DecorView的 dispatchTouchEvent()方法也返回true, 還是畫(huà)一個(gè)流程圖(這里沒(méi)有畫(huà)出onInterceptTouchEvent()方法)給大家理清下

    從 上面的流程圖得出一個(gè)結(jié)論,Touch事件是從頂層的View一直往下分發(fā)到手指按下的最里面的View,如果這個(gè)View的 onTouchEvent()返回false,即不消費(fèi)Touch事件,這個(gè)Touch事件就會(huì)向上找父布局調(diào)用其父布局的onTouchEvent() 處理,如果這個(gè)View返回true,表示消費(fèi)了Touch事件,就不調(diào)用父布局的onTouchEvent()

     

    接下來(lái)我們用一個(gè)自定義的ViewGroup來(lái)替換RelativeLayout,自定義ViewGroup代碼如下

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. package com.example.androidtouchevent;  

    2.   

    3. import android.content.Context;  

    4. import android.util.AttributeSet;  

    5. import android.view.MotionEvent;  

    6. import android.widget.RelativeLayout;  

    7.   

    8. public class CustomLayout extends RelativeLayout {  

    9.       

    10.     public CustomLayout(Context context, AttributeSet attrs) {  

    11.         super(context, attrs, 0);  

    12.     }  

    13.   

    14.     public CustomLayout(Context context, AttributeSet attrs, int defStyle) {  

    15.         super(context, attrs, defStyle);  

    16.     }  

    17.   

    18.     @Override  

    19.     public boolean onTouchEvent(MotionEvent event) {  

    20.         return super.onTouchEvent(event);  

    21.     }  

    22.   

    23.     @Override  

    24.     public boolean onInterceptTouchEvent(MotionEvent ev) {  

    25.         return true;  

    26.     }  

    27.       

    28.   

    29. }  

    我們就重寫(xiě)了onInterceptTouchEvent(),返回true, RelativeLayout默認(rèn)是返回false, 然后再CustomLayout布局中加一個(gè)Button ,如下圖

    我們這次不從DecorView的dispatchTouchEvent()分析了,直接從CustomLayout的dispatchTouchEvent()分析

    我 們先看ACTION_DOWN 來(lái)到25行,由于我們重寫(xiě)了onInterceptTouchEvent()返回true, 所以不走這個(gè)if里面,直接往下看代碼,來(lái)到84行, target為null,所以進(jìn)入if方法里面,直接調(diào)用super.dispatchTouchEvent()方法, 也就是View的dispatchTouchEvent()方法,而在View的dispatchTouchEvent()方法中是直接調(diào)用View的 onTouchEvent()方法,但是CustomLayout重寫(xiě)了onTouchEvent(),所以這里還是調(diào)用CustomLayout的 onTouchEvent(), 這個(gè)方法返回false, 不消費(fèi)Touch事件,所以不會(huì)在觸發(fā)ACTION_MOVE,ACTION_UP等事件了,這里我再畫(huà)一個(gè)流程圖吧(含有 onInterceptTouchEvent()方法的)

     

    好了,就分析到這里吧,差不多分析完了,還有一種情況沒(méi)有分析到,例如我將CustomLayout的代碼改成下面的情形,Touch事件又是怎么分發(fā)的呢?我這里就不帶大家分析了

    [java] view plaincopy在CODE上查看代碼片派生到我的代碼片

    1. package com.example.androidtouchevent;  

    2.   

    3. import android.content.Context;  

    4. import android.util.AttributeSet;  

    5. import android.view.MotionEvent;  

    6. import android.widget.RelativeLayout;  

    7.   

    8. public class CustomLayout extends RelativeLayout {  

    9.       

    10.     public CustomLayout(Context context, AttributeSet attrs) {  

    11.         super(context, attrs, 0);  

    12.     }  

    13.   

    14.     public CustomLayout(Context context, AttributeSet attrs, int defStyle) {  

    15.         super(context, attrs, defStyle);  

    16.     }  

    17.   

    18.     @Override  

    19.     public boolean onTouchEvent(MotionEvent event) {  

    20.         return super.onTouchEvent(event);  

    21.     }  

    22.   

    23.     @Override  

    24.     public boolean onInterceptTouchEvent(MotionEvent ev) {  

    25.         if(ev.getAction() == MotionEvent.ACTION_MOVE){  

    26.             return true;  

    27.         }  

    28.         return super.onInterceptTouchEvent(ev);  

    29.     }  

    30.       

    31.   

    32. }  

    這篇文章的篇幅有點(diǎn)長(zhǎng),如果你想了解Touch事件的分發(fā)機(jī)制,你一定要認(rèn)真看完,下面來(lái)總結(jié)一下吧

    1.Activity的最頂層Window是PhoneWindow,PhoneWindow的最頂層View是DecorView

    2.一個(gè)clickable或者longClickable的View會(huì)永遠(yuǎn)消費(fèi)Touch事件,不管他是enabled還是disabled的

    3.View的長(zhǎng)按事件是在ACTION_DOWN中執(zhí)行,要想執(zhí)行長(zhǎng)按事件該View必須是longClickable的,并且不能產(chǎn)生ACTION_MOVE

    4.View的點(diǎn)擊事件是在ACTION_UP中執(zhí)行,想要執(zhí)行點(diǎn)擊事件的前提是消費(fèi)了ACTION_DOWN和ACTION_MOVE,并且沒(méi)有設(shè)置OnLongClickListener的情況下,如設(shè)置了OnLongClickListener的情況,則必須使onLongClick()返回false

    5.如果View設(shè)置了onTouchListener了,并且onTouch()方法返回true,則不執(zhí)行View的onTouchEvent()方法,也表示View消費(fèi)了Touch事件,返回false則繼續(xù)執(zhí)行onTouchEvent()

    6.Touch 事件是從最頂層的View一直分發(fā)到手指touch的最里層的View,如果最里層View消費(fèi)了ACTION_DOWN事件(設(shè)置 onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才會(huì)觸發(fā)ACTION_MOVE,ACTION_UP的發(fā)生,如果某個(gè)ViewGroup攔截了 Touch事件,則Touch事件交給ViewGroup處理

    7.Touch 事件的分發(fā)過(guò)程中,如果消費(fèi)了ACTION_DOWN,而在分發(fā)ACTION_MOVE的時(shí)候,某個(gè)ViewGroup攔截了Touch事件,就像上面那 個(gè)自定義CustomLayout,則會(huì)將ACTION_CANCEL分發(fā)給該ViewGroup下面的Touch到的View,然后將Touch事件交 給ViewGroup處理,并返回true

     

    ?



     

    穩(wěn)定

    產(chǎn)品高可用性高并發(fā)

    貼心

    項(xiàng)目群及時(shí)溝通

    專業(yè)

    產(chǎn)品經(jīng)理1v1支持

    快速

    MVP模式小步快跑

    承諾

    我們選擇聲譽(yù)

    堅(jiān)持

    10年專注高端品質(zhì)開(kāi)發(fā)
    • 返回頂部
    日韩一级片免费观看| 精品久久久久久久国产潘金莲| 国产精品igao视频| 日韩人妻无码中文字幕视频| 国产精品电影一区二区三区| 精品videossexfreeohdbbw| 国产精品青草久久久久婷婷| 538精品视频在线观看| 99久久精品免费精品国产| 久久精品亚洲视频| 亚洲AV无码成人精品区蜜桃 | 国产精品日本一区二区在线播放| 久久国产精品二国产精品| 国产精品无打码在线播放| 亚洲乱码日产精品a级毛片久久| 日本免费精品一区二区三区| 精品性影院一区二区三区内射| 99久久99久久精品| 久久国产精品一国产精品| 久久青青草原精品影院| 亚洲色精品vr一区二区三区| 中文国产成人精品久久app| 精品久久久久久国产牛牛app| 国产精品99久久精品爆乳| 国产精品成人无码久久久久久 | 久久国产热这里只有精品| 精品久久久久久久中文字幕 | 99爱在线精品视频网站| 久久综合久久自在自线精品自| 97麻豆精品国产自产在线观看| 午夜精品美女写真福利| 99re热精品视频国产免费| 1313午夜精品理伦片| 91午夜精品亚洲一区二区三区 | 国产精品不卡在线| 国产大片91精品免费观看男同| 日韩亚洲国产综合久久久| 日韩av片无码一区二区不卡电影| 麻豆人妻少妇精品无码专区| 亚洲av午夜成人片精品电影| A级精品国产片在线观看|