亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android Touch事件分發過程詳解

2020-04-11 11:44:10
字體:
來源:轉載
供稿:網友

本文以實例形式講述了Android Touch事件分發過程,對于深入理解與掌握Android程序設計有很大的幫助作用。具體分析如下:

首先,從一個簡單示例入手:

先看一個示例如下圖所示:

布局文件 :

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"   xmlns:tools="http://schemas.android.com/tools"   android:id="@+id/container"   android:layout_width="match_parent"   android:layout_height="match_parent"   android:layout_gravity="center"   tools:context="com.example.touch_event.MainActivity"   tools:ignore="MergeRootFrame" >    <Button     android:id="@+id/my_button"     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:text="@string/hello_world" />  </FrameLayout> 

MainActivity文件:

public class MainActivity extends Activity {    @Override   protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);      Button mBtn = (Button) findViewById(R.id.my_button);     mBtn.setOnTouchListener(new OnTouchListener() {        @Override       public boolean onTouch(View v, MotionEvent event) {         Log.d("", "### onTouch : " + event.getAction());         return false;       }     });     mBtn.setOnClickListener(new OnClickListener() {        @Override       public void onClick(View v) {         Log.d("", "### onClick : " + v);       }     });    }    @Override   public boolean dispatchTouchEvent(MotionEvent ev) {     Log.d("", "### activity dispatchTouchEvent");     return super.dispatchTouchEvent(ev);   } } 

當用戶點擊按鈕時會輸出如下Log:

08-31 03:03:56.116: D/(1560): ### activity dispatchTouchEvent 08-31 03:03:56.116: D/(1560): ### onTouch : 0 08-31 03:03:56.196: D/(1560): ### activity dispatchTouchEvent 08-31 03:03:56.196: D/(1560): ### onTouch : 1 08-31 03:03:56.196: D/(1560): ### onClick : android.widget.Button{52860d98 VFED..C. ...PH... 0,0-1080,144 #7f05003d app:id/my_button} 

我們可以看到首先執行了Activity中的dispatchTouchEvent方法,然后執行了onTouch方法,然后再是dispatchTouchEvent --> onTouch, 最后才是執行按鈕的點擊事件。這里我們可能有個疑問,為什么dispatchTouchEvent和onTouch都執行了兩次,而onClick才執行了一次 ? 為什么兩次的Touch事件的action不一樣,action 0 和 action 1到底代表了什么 ?

覆寫過onTouchEvent的朋友知道,一般來說我們在該方法體內都會處理集中touch類型的事件,有ACTION_DOWN、ACTION_MOVE、ACTION_UP等,不過上面我們的例子中并沒有移動,只是單純的按下、抬起。因此,我們的觸摸事件也只有按下、抬起,因此有2次touch事件,而action分別為0和1。我們看看MotionEvent中的一些變量定義吧:

public final class MotionEvent extends InputEvent implements Parcelable { // 代碼省略   public static final int ACTION_DOWN       = 0;  // 按下事件   public static final int ACTION_UP        = 1;  // 抬起事件    public static final int ACTION_MOVE       = 2;  // 手勢移動事件   public static final int ACTION_CANCEL      = 3;  // 取消  // 代碼省略 } 

可以看到,代表按下的事件為0,抬起事件為1,也證實了我們上面所說的。

在看另外兩個場景:

1、我們點擊按鈕外的區域,輸出Log如下 :

08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent08-31  03:04:45.512: D/(1560): ### activity dispatchTouchEvent 

2、我們在onTouch函數中返回true, 輸出Log如下 :

08-31 03:06:04.764: D/(1612): ### activity dispatchTouchEvent 08-31 03:06:04.764: D/(1612): ### onTouch : 0 08-31 03:06:04.868: D/(1612): ### activity dispatchTouchEvent 08-31 03:06:04.868: D/(1612): ### onTouch : 1 

以上兩個場景為什么會這樣呢 ?   我們繼續往下看吧。

Android Touch事件分發

那么整個事件分發的流程是怎樣的呢 ? 

簡單來說就是用戶觸摸手機屏幕會產生一個觸摸消息,最終這個觸摸消息會被傳送到ViewRoot ( 看4.2的源碼時這個類改成了ViewRootImpl )的InputHandler,ViewRoot是GUI管理系統與GUI呈現系統之間的橋梁,根據ViewRoot的定義,發現它并不是一個View類型,而是一個Handler。InputHandler是一個接口類型,用于處理KeyEvent和TouchEvent類型的事件,我們看看源碼 :

public final class ViewRoot extends Handler implements ViewParent,     View.AttachInfo.Callbacks {       // 代碼省略   private final InputHandler mInputHandler = new InputHandler() {     public void handleKey(KeyEvent event, Runnable finishedCallback) {       startInputEvent(finishedCallback);       dispatchKey(event, true);     }     public void handleMotion(MotionEvent event, Runnable finishedCallback) {       startInputEvent(finishedCallback);       dispatchMotion(event, true);   // 1、handle 觸摸消息     }   };     // 代碼省略   // 2、分發觸摸消息   private void dispatchMotion(MotionEvent event, boolean sendDone) {     int source = event.getSource();     if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {       dispatchPointer(event, sendDone);   // 分發觸摸消息     } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {       dispatchTrackball(event, sendDone);     } else {       // TODO       Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);       if (sendDone) {         finishInputEvent();       }     }   }   // 3、通過Handler投遞消息   private void dispatchPointer(MotionEvent event, boolean sendDone) {     Message msg = obtainMessage(DISPATCH_POINTER);     msg.obj = event;     msg.arg1 = sendDone ? 1 : 0;     sendMessageAtTime(msg, event.getEventTime());   }   @Override   public void handleMessage(Message msg) {      // ViewRoot覆寫handlerMessage來處理各種消息     switch (msg.what) {       // 代碼省略     case DO_TRAVERSAL:       if (mProfile) {         Debug.startMethodTracing("ViewRoot");       }        performTraversals();        if (mProfile) {         Debug.stopMethodTracing();         mProfile = false;       }       break;      case DISPATCH_POINTER: {    // 4、處理DISPATCH_POINTER類型的消息,即觸摸屏幕的消息       MotionEvent event = (MotionEvent) msg.obj;       try {         deliverPointerEvent(event); // 5、處理觸摸消息       } finally {         event.recycle();         if (msg.arg1 != 0) {           finishInputEvent();         }         if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");       }     } break;     // 代碼省略   }   // 6、真正的處理事件   private void deliverPointerEvent(MotionEvent event) {     if (mTranslator != null) {       mTranslator.translateEventInScreenToAppWindow(event);     }     boolean handled;     if (mView != null && mAdded) {       // enter touch mode on the down       boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;       if (isDown) {         ensureTouchMode(true);  // 如果是ACTION_DOWN事件則進入觸摸模式,否則為按鍵模式。       }       if(Config.LOGV) {         captureMotionLog("captureDispatchPointer", event);       }       if (mCurScrollY != 0) {         event.offsetLocation(0, mCurScrollY);  // 物理坐標向邏輯坐標的轉換       }       if (MEASURE_LATENCY) {         lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());       }       // 7、分發事件,如果是窗口類型,則這里的mView對應的就是PhonwWindow中的DecorView,否則為根視圖的ViewGroup。       handled = mView.dispatchTouchEvent(event);       // 代碼省略       }   }   // 代碼省略 }  

經過層層迷霧,不管代碼7處的mView是DecorView還是非窗口界面的根視圖,其本質都是ViewGroup,即觸摸事件最終被根視圖ViewGroup進行分發?。。?br />        我們就以Activity為例來分析這個過程,我們知道顯示出來的Activity有一個頂層窗口,這個窗口的實現類是PhoneWindow, PhoneWindow中的內容區域是一個DecorView類型的View,這個View這就是我們在手機上看到的內容,這個DecorView是FrameLayout的子類,Activity的的dispatchTouchEvent實際上就是調用PhoneWindow的dispatchTouchEvent,我們看看源代碼吧,進入Activity的dispatchTouchEvent函數 :

public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {      onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) {   // 1、調用的是PhoneWindow的superDispatchTouchEvent(ev)       return true;    }    return onTouchEvent(ev);  }   public void onUserInteraction() {  } 

可以看到,如果事件為按下事件,則會進入到onUserInteraction()這個函數,該函數為空實現,我們暫且不管它。繼續看,發現touch事件的分發調用了getWindow().superDispatchTouchEvent(ev)函數,getWindow()獲取到的實例的類型為PhoneWindow類型,你可以在你的Activity類中使用如下方式查看getWindow()獲取到的類型:

Log.d("", "### Activiti中getWindow()獲取的類型是 : " + this.getWindow()) ; 

輸出:

08-31 03:40:17.036: D/(1688): ### Activiti中getWindow()獲取的類型是 : com.android.internal.policy.impl.PhoneWindow@5287fe38 

OK,廢話不多說,我們還是繼續看PhoneWindow中的superDispatchTouchEvent函數吧。

@Override public boolean superDispatchTouchEvent(MotionEvent event) {   return mDecor.superDispatchTouchEvent(event); } 

恩,調用的是mDecor的superDispatchTouchEvent(event)函數,這個mDecor就是我們上面所說的DecorView類型,也就是我們看到的Activity上的所有內容的一個頂層ViewGroup,即整個ViewTree的根節點??纯此穆暶靼?。

// This is the top-level view of the window, containing the window decor. private DecorView mDecor; 

DecorView

那么我繼續看看DecorView到底是個什么玩意兒吧。

  private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {     /* package */int mDefaultOpacity = PixelFormat.OPAQUE;      /** The feature ID of the panel, or -1 if this is the application's DecorView */     private final int mFeatureId;      private final Rect mDrawingBounds = new Rect();      private final Rect mBackgroundPadding = new Rect();      private final Rect mFramePadding = new Rect();      private final Rect mFrameOffsets = new Rect();      private boolean mChanging;      private Drawable mMenuBackground;     private boolean mWatchingForMenu;     private int mDownY;      public DecorView(Context context, int featureId) {       super(context);       mFeatureId = featureId;     }      @Override     public boolean dispatchKeyEvent(KeyEvent event) {       final int keyCode = event.getKeyCode();       // 代碼省略       return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)           : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);     }      @Override     public boolean dispatchTouchEvent(MotionEvent ev) {       final Callback cb = getCallback();       return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super           .dispatchTouchEvent(ev);     }      @Override     public boolean dispatchTrackballEvent(MotionEvent ev) {       final Callback cb = getCallback();       return cb != null && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super           .dispatchTrackballEvent(ev);     }      public boolean superDispatchKeyEvent(KeyEvent event) {       return super.dispatchKeyEvent(event);     }      public boolean superDispatchTouchEvent(MotionEvent event) {       return super.dispatchTouchEvent(event);     }      public boolean superDispatchTrackballEvent(MotionEvent event) {       return super.dispatchTrackballEvent(event);     }      @Override     public boolean onTouchEvent(MotionEvent event) {       return onInterceptTouchEvent(event);     } // 代碼省略 } 

可以看到,DecorView繼承自FrameLayout, 它對于touch事件的分發( dispatchTouchEvent )、處理都是交給super類來處理,也就是FrameLayout來處理,我們在FrameLayout中沒有看到相應的實現,那繼續跟蹤到FrameLayout的父類,即ViewGroup,我們看到了dispatchTouchEvent的實現,那我們就先看ViewGroup (Android 2.3 源碼)是如何進行事件分發的吧。

ViewGroup的Touch事件分發

/**  * {@inheritDoc}  */ @Override public boolean dispatchTouchEvent(MotionEvent ev) {   if (!onFilterTouchEventForSecurity(ev)) {     return false;   }    final int action = ev.getAction();   final float xf = ev.getX();   final float yf = ev.getY();   final float scrolledXFloat = xf + mScrollX;   final float scrolledYFloat = yf + mScrollY;   final Rect frame = mTempRect;    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (action == MotionEvent.ACTION_DOWN) {     if (mMotionTarget != null) {       // this is weird, we got a pen down, but we thought it was       // already down!       // XXX: We should probably send an ACTION_UP to the current       // target.       mMotionTarget = null;     }     // If we're disallowing intercept or if we're allowing and we didn't     // intercept     if (disallowIntercept || !onInterceptTouchEvent(ev))     // 1、是否禁用攔截、是否攔截事件       // reset this event's action (just to protect ourselves)       ev.setAction(MotionEvent.ACTION_DOWN);       // We know we want to dispatch the event down, find a child       // who can handle it, start with the front-most child.       final int scrolledXInt = (int) scrolledXFloat;       final int scrolledYInt = (int) scrolledYFloat;       final View[] children = mChildren;       final int count = mChildrenCount;        for (int i = count - 1; i >= 0; i--)    // 2、迭代所有子view,查找觸摸事件在哪個子view的坐標范圍內         final View child = children[i];         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE             || child.getAnimation() != null) {           child.getHitRect(frame);        // 3、獲取child的坐標范圍           if (frame.contains(scrolledXInt, scrolledYInt))  // 4、判斷發生該事件坐標是否在該child坐標范圍內             // offset the event to the view's coordinate system             final float xc = scrolledXFloat - child.mLeft;             final float yc = scrolledYFloat - child.mTop;             ev.setLocation(xc, yc);             child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;             if (child.dispatchTouchEvent(ev))   // 5、child處理該事件               // Event handled, we have a target now.               mMotionTarget = child;               return true;             }             // The event didn't get handled, try the next view.             // Don't reset the event's location, it's not             // necessary here.           }         }       }     }   }    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||       (action == MotionEvent.ACTION_CANCEL);    if (isUpOrCancel) {     // Note, we've already copied the previous state to our local     // variable, so this takes effect on the next event     mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;   }    // The event wasn't an ACTION_DOWN, dispatch it to our target if   // we have one.   final View target = mMotionTarget;   if (target == null) {     // We don't have a target, this means we're handling the     // event as a regular view.     ev.setLocation(xf, yf);     if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {       ev.setAction(MotionEvent.ACTION_CANCEL);       mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;     }     return super.dispatchTouchEvent(ev);   }    // if have a target, see if we're allowed to and want to intercept its   // events   if (!disallowIntercept && onInterceptTouchEvent(ev)) {     final float xc = scrolledXFloat - (float) target.mLeft;     final float yc = scrolledYFloat - (float) target.mTop;     mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;     ev.setAction(MotionEvent.ACTION_CANCEL);     ev.setLocation(xc, yc);     if (!target.dispatchTouchEvent(ev)) {       // target didn't handle ACTION_CANCEL. not much we can do       // but they should have.     }     // clear the target     mMotionTarget = null;     // Don't dispatch this event to our own view, because we already     // saw it when intercepting; we just want to give the following     // event to the normal onTouchEvent().     return true;   }    if (isUpOrCancel) {     mMotionTarget = null;   }    // finally offset the event to the target's coordinate system and   // dispatch the event.   final float xc = scrolledXFloat - (float) target.mLeft;   final float yc = scrolledYFloat - (float) target.mTop;   ev.setLocation(xc, yc);    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {     ev.setAction(MotionEvent.ACTION_CANCEL);     target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;     mMotionTarget = null;   }    return target.dispatchTouchEvent(ev); } 

這個函數代碼比較長,我們只看上文中標注的幾個關鍵點。首先在代碼1處可以看到一個條件判斷,如果disallowIntercept和!onInterceptTouchEvent(ev)兩者有一個為true,就會進入到這個條件判斷中。disallowIntercept是指是否禁用掉事件攔截的功能,默認是false,也可以通過調用requestDisallowInterceptTouchEvent方法對這個值進行修改。那么當第一個值為false的時候就會完全依賴第二個值來決定是否可以進入到條件判斷的內部,第二個值是什么呢?onInterceptTouchEvent就是ViewGroup對事件進行攔截的一個函數,返回該函數返回false則表示不攔截事件,反之則表示攔截。第二個條件是是對onInterceptTouchEvent方法的返回值取反,也就是說如果我們在onInterceptTouchEvent方法中返回false,就會讓第二個值為true,從而進入到條件判斷的內部,如果我們在onInterceptTouchEvent方法中返回true,就會讓第二個值的整體變為false,從而跳出了這個條件判斷。例如我們需要實現ListView滑動刪除某一項的功能,那么可以通過在onInterceptTouchEvent返回true,并且在onTouchEvent中實現相關的判斷邏輯,從而實現該功能。

進入代碼1內部的if后,有一個for循環,遍歷了當前ViewGroup下的所有子child view,如果觸摸該事件的坐標在某個child view的坐標范圍內,那么該child view來處理這個觸摸事件,即調用該child view的dispatchTouchEvent。如果該child view是ViewGroup類型,那么繼續執行上面的判斷,并且遍歷子view;如果該child view不是ViewGroup類型,那么直接調用的是View中的dispatchTouchEvent方法,除非這個child view的類型覆寫了該方法。我們看看View中的dispatchTouchEvent函數:

View的Touch事件分發

/**  * Pass the touch screen motion event down to the target view, or this  * view if it is the target.  *  * @param event The motion event to be dispatched.  * @return True if the event was handled by the view, false otherwise.  */ public boolean dispatchTouchEvent(MotionEvent event) {   if (!onFilterTouchEventForSecurity(event)) {     return false;   }    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&       mOnTouchListener.onTouch(this, event)) {     return true;   }   return onTouchEvent(event); } 

該函數中,首先判斷該事件是否符合安全策略,然后判斷該view是否是enable的 ,以及是否設置了Touch Listener,mOnTouchListener即我們通過setOnTouchListener設置的。

/**  * 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) {   mOnTouchListener = l; } 

如果mOnTouchListener.onTouch(this, event)返回false則繼續執行onTouchEvent(event);如果mOnTouchListener.onTouch(this, event)返回true,則表示該事件被消費了,不再傳遞,因此也不會執行onTouchEvent(event)。這也驗證了我們上文中留下的場景2,當onTouch函數返回true時,點擊按鈕,但我們的點擊事件沒有執行。那么我們還是先來看看onTouchEvent(event)函數到底做了什么吧。

/**  * Implement this method to handle touch screen motion events.  *  * @param event The motion event.  * @return True if the event was handled, false otherwise.  */ public boolean onTouchEvent(MotionEvent event) {   final int viewFlags = mViewFlags;    if ((viewFlags & ENABLED_MASK) == DISABLED)    // 1、判斷該view是否enable     // 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;     }   }    if (((viewFlags & CLICKABLE) == CLICKABLE ||       (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) // 2、是否是clickable或者long clickable     switch (event.getAction()) {       case MotionEvent.ACTION_UP:          // 抬起事件         boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;         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))   // post                 performClick();     // 3、點擊事件處理               }             }           }            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;     }     return true;   }    return false; } 

我們看到,在onTouchEvent函數中就是對ACTION_UP、ACTION_DOWN、ACTION_MOVE等幾個事件進行處理,而最重要的就是UP事件了,因為這個里面包含了對用戶點擊事件的處理,或者是說對于用戶而言相對重要一點,因此放在了第一個case中。在ACTION_UP事件中會判斷該view是否enable、是否clickable、是否獲取到了焦點,然后我們看到會通過post方法將一個PerformClick對象投遞給UI線程,如果投遞失敗則直接調用performClick函數執行點擊事件。

/**  * Causes the Runnable to be added to the message queue.  * The runnable will be run on the user interface thread.  *  * @param action The Runnable that will be executed.  *  * @return Returns true if the Runnable was successfully placed in to the  *     message queue. Returns false on failure, usually because the  *     looper processing the message queue is exiting.  */ public boolean post(Runnable action) {   Handler handler;   if (mAttachInfo != null) {     handler = mAttachInfo.mHandler;   } else {     // Assume that post will succeed later     ViewRoot.getRunQueue().post(action);     return true;   }    return handler.post(action); } 

我們看看PerformClick類吧。

private final class PerformClick implements Runnable {   public void run() {     performClick();   } } 

可以看到,其內部就是包裝了View類中的performClick()方法。再看performClick()方法:

/**  * 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(OnClickListener l) {    if (!isClickable()) {      setClickable(true);    }    mOnClickListener = l;  }   /**  * Call this view's OnClickListener, if it is defined.  *  * @return True there was an assigned OnClickListener that was called, false  *     otherwise is returned.  */  public boolean performClick() {    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);     if (mOnClickListener != null) {      playSoundEffect(SoundEffectConstants.CLICK);      mOnClickListener.onClick(this);      return true;    }     return false;  } 

代碼很簡單,主要就是調用了mOnClickListener.onClick(this);方法,即執行用戶通過setOnClickListener設置進來的點擊事件處理Listener。
 
總結

用戶觸摸屏幕產生一個觸摸消息,系統底層將該消息轉發給ViewRoot ( ViewRootImpl ),ViewRoot產生一個DISPATCHE_POINTER的消息,并且在handleMessage中處理該消息,最終會通過deliverPointerEvent(MotionEvent event)來處理該消息。在該函數中會調用mView.dispatchTouchEvent(event)來分發消息,該mView是一個ViewGroup類型,因此是ViewGroup的dispatchTouchEvent(event),在該函數中會遍歷所有的child view,找到該事件的觸發的左邊與每個child view的坐標進行對比,如果觸摸的坐標在該child view的范圍內,則由該child view進行處理。如果該child view是ViewGroup類型,則繼續上一步的查找過程;否則執行View中的dispatchTouchEvent(event)函數。在View的dispatchTouchEvent(event)中首先判斷該控件是否enale以及mOnTouchListent是否為空,如果mOnTouchListener不為空則執行mOnTouchListener.onTouch(event)方法,如果該方法返回false則再執行View中的onTouchEvent(event)方法,并且在該方法中執行mOnClickListener.onClick(this, event) ;方法; 如果mOnTouchListener.onTouch(event)返回true則不會執行onTouchEvent方法,因此點擊事件也不會被執行。

粗略的流程圖如下 :

相信本文所述對大家進一步深入掌握Android程序設計有一定的借鑒價值。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美极品美女视频网站在线观看免费| 欧美视频在线观看免费| 亚洲精品日韩丝袜精品| 欧美精品激情在线| 欧美成人精品激情在线观看| 国产精品麻豆va在线播放| 精品国偷自产在线视频| 亚洲人精选亚洲人成在线| 欧美多人乱p欧美4p久久| 亚洲性猛交xxxxwww| 亚洲欧美日韩中文在线制服| 欧美日韩国产成人在线| 福利一区福利二区微拍刺激| 国产精品国产三级国产专播精品人| 国产综合久久久久| 国产精品视频网址| 2019国产精品自在线拍国产不卡| 亚洲欧美日韩一区二区在线| 97精品国产97久久久久久免费| 性色av一区二区咪爱| 中文.日本.精品| 亚洲精品电影在线观看| 91系列在线播放| 国产精品视频免费在线观看| 懂色aⅴ精品一区二区三区蜜月| 这里只有精品在线播放| 亚洲成人在线网| 日韩精品免费在线播放| 北条麻妃一区二区三区中文字幕| 欧洲亚洲免费视频| 久久福利视频导航| 91av免费观看91av精品在线| 国产成人精品视频| 久久久国产精品一区| 国产精品久久久久一区二区| 91色在线视频| 日韩在线中文字| 91在线观看免费| 色无极亚洲影院| 成人黄色生活片| 国语自产精品视频在线看抢先版图片| 自拍偷拍亚洲一区| 日韩美女视频中文字幕| 7m精品福利视频导航| 66m—66摸成人免费视频| 91精品国产综合久久香蕉922| 国产精品视频网| 88国产精品欧美一区二区三区| 欧美成人四级hd版| 另类视频在线观看| 日韩激情视频在线播放| 国产成人综合一区二区三区| 色av吧综合网| 亚洲xxxx视频| 亚洲自拍中文字幕| 青青久久av北条麻妃海外网| 久久久精品国产| 精品国产999| 国产欧美日韩精品在线观看| 日韩大片免费观看视频播放| 97在线视频免费观看| 欧美日韩国产一中文字不卡| 日韩精品免费综合视频在线播放| 欧美另类高清videos| 久久伊人91精品综合网站| 久久人人爽人人爽人人片av高清| 亚洲电影免费在线观看| 岛国av一区二区三区| 国产精品大陆在线观看| www.日韩.com| 日韩精品极品在线观看| 国产脚交av在线一区二区| 一本一本久久a久久精品牛牛影视| www.欧美精品| 国产精品一区二区久久国产| 欧美国产高跟鞋裸体秀xxxhd| 日韩精品在线观看一区| 欧美理论在线观看| 国产视频自拍一区| 超碰精品一区二区三区乱码| 亚洲成人精品av| 成人春色激情网| 精品中文字幕在线2019| 久久99热精品这里久久精品| 精品国产一区二区三区四区在线观看| 中文字幕免费国产精品| 亚洲国产精品高清久久久| 亚洲一区二区久久| 国产不卡在线观看| 亚洲色图激情小说| 最近免费中文字幕视频2019| 国产乱肥老妇国产一区二| 欧美激情第三页| 亚洲国产精品va在线观看黑人| 日产日韩在线亚洲欧美| 97涩涩爰在线观看亚洲| 国产一级揄自揄精品视频| 精品国产欧美一区二区三区成人| 亚洲最大av网站| 91久久久久久久久久久| 欧美丰满老妇厨房牲生活| 北条麻妃一区二区三区中文字幕| 在线播放国产一区中文字幕剧情欧美| 91在线观看免费网站| 国产精品人成电影在线观看| 国产欧美日韩高清| 欧美国产日韩xxxxx| 伊人伊成久久人综合网小说| 国产精品久久不能| 欧美国产日韩一区二区三区| 国产精品678| 日韩中文字幕在线看| 日韩精品一二三四区| 久久视频国产精品免费视频在线| 亚洲成年人在线播放| 日韩av电影院| 欧美怡春院一区二区三区| 亚洲亚裔videos黑人hd| 国产精品日本精品| 亚洲第一页中文字幕| 久久精品国产成人| 成人福利视频在线观看| 国产成人91久久精品| 日韩亚洲欧美中文高清在线| 国产精品爽爽ⅴa在线观看| 欧美精品在线网站| 一区二区三区回区在观看免费视频| 成人精品视频在线| 91产国在线观看动作片喷水| 在线成人一区二区| 亚洲成人动漫在线播放| 国内精品一区二区三区四区| 欧美色xxxx| 全色精品综合影院| 欧美日韩福利视频| 亚洲人成在线观看网站高清| 久久久久久久久国产| 中文字幕亚洲专区| 国产91精品久久久久久久| 另类少妇人与禽zozz0性伦| 久久精品中文字幕一区| 国产精品对白刺激| 欧美激情高清视频| 精品国产一区二区三区久久| 国内伊人久久久久久网站视频| 久久久久久网址| 亚洲网站在线观看| 亚洲精品福利在线| 成人免费看片视频| 欧美风情在线观看| 成人乱色短篇合集| 亚洲精品电影在线观看| 亚洲免费一在线| 亚洲欧美综合图区| 97视频在线观看视频免费视频| 日韩国产精品亚洲а∨天堂免| 91久久久久久| 精品国内自产拍在线观看| 国产美女搞久久| 美女av一区二区三区| 国产精品久久久亚洲| 久久精品国产亚洲| 亚洲欧洲日韩国产| 国产成人自拍视频在线观看|