亚洲乱码卡一卡二卡三永久-亚洲乱码一二三四区-亚洲乱码一区二区三区在线观看-亚洲伦理一区-成人在色线视频在线观看免费大全-成人在线91

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

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

 

ViewGroup的事件分發機制

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

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

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  

  2.   

  3.         //如果是按下狀態就調用onUserInteraction()方法,onUserInteraction()方法  

  4.         //是個空的方法, 我們直接跳過這里看下面的實現  

  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,這個事件就交給Activity  

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

  15.         return onTouchEvent(ev);  

  16.     }  

這 個方法中我們還是比較關心getWindow()的superDispatchTouchEvent()方法,getWindow()返回當前 Activity的頂層窗口Window對象,我們直接看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);  

這個是個抽象方法,所以我們直接找到其子類來看看superDispatchTouchEvent()方法的具體邏輯實現,Window的唯一子類是PhoneWindow,我們就看看PhoneWindow的superDispatchTouchEvent()方法

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

  1. public boolean superDispatchTouchEvent(KeyEvent event) {  

  2.         return mDecor.superDispatcTouchEvent(event);  

  3.     }  

里 面直接調用DecorView類的superDispatchTouchEvent()方法,或許很多人不了解DecorView這個 類,DecorView是PhoneWindow的一個final的內部類并且繼承FrameLayout的,也是Window界面的最頂層的View對 象,這是什么意思呢?別著急,我們接著往下看
我們先新建一個項目,取名AndroidTouchEvent,然后直接用模擬器運行項目, 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工具來查看下MainActivity的View的層次結構,如下圖



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

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

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

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

  1. public boolean superDispatchTouchEvent(MotionEvent event) {  

  2.             return super.dispatchTouchEvent(event);  

  3.         }  

在里面調用了父類FrameLayout的dispatchTouchEvent()方法,而FrameLayout中并沒有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.        //這個值默認是false, 然后我們可以通過requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法  

  14.        //來改變disallowIntercept的值  

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

  16.   

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

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

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

  20.            if (mMotionTarget != null) {  

  21.                mMotionTarget = null;  

  22.            }  

  23.   

  24.            //disallowIntercept默認是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正在執行動畫, 表示該View才  

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

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

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

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

  40.                        child.getHitRect(frame);  

  41.                          

  42.                        //如Touch到屏幕上的點在該子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.                            //調用該子View的dispatchTouchEvent()方法  

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

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

  53.                             //該事件被消費了,設置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設置為默認的false  

  73.         //假如我們調用了requestDisallowInterceptTouchEvent()方法來設置disallowIntercept為true  

  74.         //當我們抬起手指或者取消Touch事件的時候要將disallowIntercept重置為false  

  75.         //所以說上面的disallowIntercept默認在我們每次ACTION_DOWN的時候都是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意味著沒有找到消費Touch事件的View, 所以我們需要調用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.        //這個if里面的代碼ACTION_DOWN不會執行,只有ACTION_MOVE  

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

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

  98.        //表示消費了此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.        //如果沒有攔截ACTION_MOVE, ACTION_DOWN的話,直接將Touch事件派發給target  

  133.        return target.dispatchTouchEvent(ev);  

  134.    }  

這個方法相對來說還是蠻長,不過所有的邏輯都寫在一起,看起來比較方便,接下來我們就具體來分析一下

 

我們點擊屏幕上面的TextView來看看Touch是如何分發的,先看看ACTION_DOWN

在 DecorView這一層會直接調用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都會將mMotionTarget設置為null, mMotionTarget是什么?我們先不管,繼續看代 碼,走到25行,  disallowIntercept默認為false,我們再看ViewGroup的onInterceptTouchEvent()方法

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

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  

  2.       return false;  

  3.   }  

直 接返回false, 繼續往下看,循環遍歷DecorView里面的Child,從上面的MainActivity的層次結構圖我們可以看出,DecorView里面只有一個 Child那就是LinearLayout, 第43行判斷Touch的位置在不在LinnearLayout上面,這是毫無疑問的,所以直接跳到51行, 調用LinearLayout的dispatchTouchEvent()方法,LinearLayout也沒有 dispatchTouchEvent()這個方法,所以也是調用ViewGroup的dispatchTouchEvent()方法,所以這個方法卡在 51行沒有繼續下去,而是去先執行LinearLayout的dispatchTouchEvent()

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

繼 續調用FrameLayout的dispatchTouchEvent()方法,和上面一樣的邏輯,下面的FrameLayout也只有一個Child, 就是RelativeLayout,FrameLayout的dispatchTouchEvent()繼續卡在51行,先執行 RelativeLayout的dispatchTouchEvent()方法

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

上面的ViewGroup的Touch事件分發就告一段落先,因為這里要調用TextView(也就是View)的dispatchTouchEvent()方法,所以我們先分析View的dispatchTouchEvent()方法在將上面的繼續下去

 

View的Touch事件分發機制

我們還是先看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.     }  

在這個方法里面,先進行了一個判斷

第一個條件mOnTouchListener就是我們調用View的setTouchListener()方法設置的

第二個條件是判斷View是否為enabled的, View一般都是enabled,除非你手動設置為disabled

第 三個條件就是OnTouchListener接口的onTouch()方法的返回值了,如果調用了setTouchListener()設置 OnTouchListener,并且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回 true,否則就執行View的onTouchEvent() 并返回View的onTouchEvent()的值
現在你了解了View的 onTouchEvent()方法和onTouch()的關系了吧,為什么Android提供了處理Touch事件onTouchEvent()方法還要 增加一個OnTouchListener接口呢?我覺得OnTouchListener接口是對處理Touch事件的屏蔽和擴展作用吧,屏蔽作用我就不舉 例介紹了,看上面的源碼就知道了,我就說下擴展吧,比如我們要打印View的Touch的點的坐標,我們可以自定義一個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的坐標 = " + event.getX() + " Y的坐標 = " + event.getY());  

  15.           

  16.         return super.onTouchEvent(event);  

  17.     }  

  18.   

  19. }  

也可以直接對View設置OnTouchListener接口,在return的時候調用下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的坐標 = " + event.getX() + " Y的坐標 = " + event.getY());  

  7.                   

  8.                 return v.onTouchEvent(event);  

  9.             }  

  10.         });  

這樣子也實現了我們所需要的功能,所以我認為OnTouchListener是對onTouchEvent()方法的一個屏蔽和擴展作用,假如你有不一樣的理解,你也可以告訴我下,這里就不糾結這個了。

我們再看View的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.       //如果設置了Touch代理,就交給代理來處理,mTouchDelegate默認是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.                   //當手指在View上面滑動超過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.   }  

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

我們在看57行的ACTION_DOWN, 新建一個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.    }  

原 來是個Runnable對象,然后使用Handler的post方法延時ViewConfiguration.getTapTimeout()執行 CheckForTap的run()方法,在run方法中先判斷view是否longClickable的,一般的View都是 false, postCheckForLongClick(ViewConfiguration.getTapTimeout())這段代碼就是執行長 按的邏輯的代碼,只有當我們設置為longClickble才會去執行 postCheckForLongClick(ViewConfiguration.getTapTimeout()),這里我就不介紹了

由于考慮到文章篇幅的問題,我就不繼續分析View的長按事件和點擊事件了,在這里我直接得出結論吧

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

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

  1. public void setOnLongClickListener(OnLongClickListener l) {  

  2.      if (!isLongClickable()) {  

  3.          setLongClickable(true);  

  4.      }  

  5.      mOnLongClickListener = l;  

  6.  }  

看到沒有,如果這個View不是longClickable的,我們就調用setLongClickable(true)方法設置為longClickable的,所以才會去執行長按方法onLongClick();
 

要 想執行點擊事件,這個View就必須要消費ACTION_DOWN和ACTION_MOVE事件,并且沒有設置OnLongClickListener的 情況下,如果設置了OnLongClickListener的情況下,需要onLongClick()返回false才能執行到onClick()方法, 也許你又會納悶,一般的View默認是不消費touch事件的,這不是和你上面說的相違背嘛,我們要向執行點擊事件必須要調用 setOnClickListener()來設置OnClickListener接口,我們看看這個方法的源碼就知道了

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

  1. public void setOnClickListener(OnClickListener l) {  

  2.      if (!isClickable()) {  

  3.          setClickable(true);  

  4.      }  

  5.      mOnClickListener = l;  

  6.  }  

所以說一個enable的并且是clickable的View是一直消費touch事件的,所以才會執行到onClick()方法


 

對 于View的Touch事件的分發機制算是告一段落了,從上面我們可以得出TextView的dispatchTouchEvent()的返回false 的,即不消費Touch事件。我們就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,由于 TextView.dispatchTouchEvent()為false, 導致mMotionTarget沒有被賦值,還是null, 繼續往下走執行RelativeLayout的dispatchTouchEvent()方法, 來到第84行, 判斷target是否為null,這個target就是mMotionTarget,滿足條件,執行92行的 super.dispatchTouchEvent(ev)代碼并返回, 這里調用的是RelativeLayout父類View的dispatchTouchEvent()方法,由于RelativeLayout沒有設置 onTouchListener, 所以這里直接調用RelativeLayout(其實就是View, 因為RelativeLayout沒有重寫onTouchEvent())的onTouchEvent()方法 由于RelativeLayout既不是clickable的也是longClickable的,所以其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,這里就執行完了RelativeLayout的 dispatchTouchEvent()方法

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

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

所 以我們點擊屏幕上面的TextView的事件分發流程是上圖那個樣子的,表示Activity的View都不消費ACTION_DOWN事件,所以就不能 在觸發ACTION_MOVE, ACTION_UP等事件了,具體是為什么?我還不太清楚,畢竟從Activity到TextView這一層是分析不出來的,估計是在底層實現的。

 

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

然后到FrameLayout的dispatchTouchEvent()方法的51行,由于 RelativeLayout.dispatchTouchEvent()返回true, 滿足條件,進入if方法體,設置mMotionTarget為RelativeLayout,注意下,這里的mMotionTarget跟 RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一個哦,因為他們是不同的方法中的,然 后返回true

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

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

 

接下來我們用一個自定義的ViewGroup來替換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. }  

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

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

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

 

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

[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. }  

這篇文章的篇幅有點長,如果你想了解Touch事件的分發機制,你一定要認真看完,下面來總結一下吧

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

2.一個clickable或者longClickable的View會永遠消費Touch事件,不管他是enabled還是disabled的

3.View的長按事件是在ACTION_DOWN中執行,要想執行長按事件該View必須是longClickable的,并且不能產生ACTION_MOVE

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

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

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

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

 

?



 

穩定

產品高可用性高并發

貼心

項目群及時溝通

專業

產品經理1v1支持

快速

MVP模式小步快跑

承諾

我們選擇聲譽

堅持

10年專注高端品質開發
  • 返回頂部
主站蜘蛛池模板: 毛片免费永久不卡视频观看 | 91av福利| 亚洲天堂网在线观看 | 欧美日韩一区二区三区高清不卡 | 欧美疯狂xxxx乱大交视频 | 在线看片黄| 大又大又黄又爽免费毛片 | 国产中文一区 | 中文字幕15页 | 最近2019在线观看中文视频 | 三级伦理片高清在线观看播放 | 日韩欧美一及在线播放 | 亚洲第一成年网站大全亚洲 | 国产第一浮力影院新路线 | 成人禁在线观看网站 | 最近的2019中文字幕国语hd | 色网站在线播放 | 国产精品果贷一区二区借贷宝 | 亚洲啊啊 | 在线日韩中文字幕 | 日本免费黄网 | 一级毛片特级毛片免费的 | 久草在现 | 久操成人 | 亚洲成a人片77777老司机 | 亚洲综合色秘密影院秘密影院 | 涩涩涩在线视频 | 男女在线播放 | 一二三区乱码一区二区三区码 | 怡红院美国十次成人影院 | 又黄又爽又成人免费视频 | 羞羞色在线 | 国产成人高清视频免费播放 | 日韩毛片在线观看 | 欧美日本综合 | www.黄色在线观看 | 成人久久18免费网站入口 | 一级一黄在线观看视频免费 | 欧美色视频网站 | 最近中文字幕2019高清8? | 亚洲人成绝费网站色ww |