欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android Touch事件分發(fā)過程詳解

 更新時(shí)間:2014年09月11日 10:20:15   投稿:shichen2014  
這篇文章主要介紹了Android Touch事件分發(fā)過程,詳細(xì)描述了Android Touch事件的主要處理流程,有助于深入理解Android程序設(shè)計(jì),需要的朋友可以參考下

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

首先,從一個(gè)簡單示例入手:

先看一個(gè)示例如下圖所示:

布局文件 :

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

當(dāng)用戶點(diǎn)擊按鈕時(shí)會輸出如下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} 

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

覆寫過onTouchEvent的朋友知道,一般來說我們在該方法體內(nèi)都會處理集中touch類型的事件,有ACTION_DOWN、ACTION_MOVE、ACTION_UP等,不過上面我們的例子中并沒有移動(dòng),只是單純的按下、抬起。因此,我們的觸摸事件也只有按下、抬起,因此有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;  // 手勢移動(dòng)事件 
  public static final int ACTION_CANCEL      = 3;  // 取消 
 // 代碼省略 
} 

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

在看另外兩個(gè)場景:

1、我們點(diǎn)擊按鈕外的區(qū)域,輸出Log如下 :

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

2、我們在onTouch函數(shù)中返回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 

以上兩個(gè)場景為什么會這樣呢 ?   我們繼續(xù)往下看吧。

Android Touch事件分發(fā)

那么整個(gè)事件分發(fā)的流程是怎樣的呢 ? 

簡單來說就是用戶觸摸手機(jī)屏幕會產(chǎn)生一個(gè)觸摸消息,最終這個(gè)觸摸消息會被傳送到ViewRoot ( 看4.2的源碼時(shí)這個(gè)類改成了ViewRootImpl )的InputHandler,ViewRoot是GUI管理系統(tǒng)與GUI呈現(xiàn)系統(tǒng)之間的橋梁,根據(jù)ViewRoot的定義,發(fā)現(xiàn)它并不是一個(gè)View類型,而是一個(gè)Handler。InputHandler是一個(gè)接口類型,用于處理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、分發(fā)觸摸消息 
  private void dispatchMotion(MotionEvent event, boolean sendDone) { 
    int source = event.getSource(); 
    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 
      dispatchPointer(event, sendDone);   // 分發(fā)觸摸消息 
    } 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事件則進(jìn)入觸摸模式,否則為按鍵模式。 
      } 
      if(Config.LOGV) { 
        captureMotionLog("captureDispatchPointer", event); 
      } 
      if (mCurScrollY != 0) { 
        event.offsetLocation(0, mCurScrollY);  // 物理坐標(biāo)向邏輯坐標(biāo)的轉(zhuǎn)換 
      } 
      if (MEASURE_LATENCY) { 
        lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); 
      } 
      // 7、分發(fā)事件,如果是窗口類型,則這里的mView對應(yīng)的就是PhonwWindow中的DecorView,否則為根視圖的ViewGroup。 
      handled = mView.dispatchTouchEvent(event); 
      // 代碼省略   
    } 
  } 
  // 代碼省略 
}  

經(jīng)過層層迷霧,不管代碼7處的mView是DecorView還是非窗口界面的根視圖,其本質(zhì)都是ViewGroup,即觸摸事件最終被根視圖ViewGroup進(jìn)行分發(fā)?。?!
        我們就以Activity為例來分析這個(gè)過程,我們知道顯示出來的Activity有一個(gè)頂層窗口,這個(gè)窗口的實(shí)現(xiàn)類是PhoneWindow, PhoneWindow中的內(nèi)容區(qū)域是一個(gè)DecorView類型的View,這個(gè)View這就是我們在手機(jī)上看到的內(nèi)容,這個(gè)DecorView是FrameLayout的子類,Activity的的dispatchTouchEvent實(shí)際上就是調(diào)用PhoneWindow的dispatchTouchEvent,我們看看源代碼吧,進(jìn)入Activity的dispatchTouchEvent函數(shù) :

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

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

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

輸出:

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

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

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

恩,調(diào)用的是mDecor的superDispatchTouchEvent(event)函數(shù),這個(gè)mDecor就是我們上面所說的DecorView類型,也就是我們看到的Activity上的所有內(nèi)容的一個(gè)頂層ViewGroup,即整個(gè)ViewTree的根節(jié)點(diǎn)??纯此穆暶靼伞?/p>

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

DecorView

那么我繼續(xù)看看DecorView到底是個(gè)什么玩意兒吧。

  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事件的分發(fā)( dispatchTouchEvent )、處理都是交給super類來處理,也就是FrameLayout來處理,我們在FrameLayout中沒有看到相應(yīng)的實(shí)現(xiàn),那繼續(xù)跟蹤到FrameLayout的父類,即ViewGroup,我們看到了dispatchTouchEvent的實(shí)現(xiàn),那我們就先看ViewGroup (Android 2.3 源碼)是如何進(jìn)行事件分發(fā)的吧。

ViewGroup的Touch事件分發(fā)

/** 
 * {@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,查找觸摸事件在哪個(gè)子view的坐標(biāo)范圍內(nèi) 
        final View child = children[i]; 
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 
            || child.getAnimation() != null) { 
          child.getHitRect(frame);        // 3、獲取child的坐標(biāo)范圍 
          if (frame.contains(scrolledXInt, scrolledYInt))  // 4、判斷發(fā)生該事件坐標(biāo)是否在該child坐標(biāo)范圍內(nèi) 
            // 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); 
} 

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

進(jìn)入代碼1內(nèi)部的if后,有一個(gè)for循環(huán),遍歷了當(dāng)前ViewGroup下的所有子child view,如果觸摸該事件的坐標(biāo)在某個(gè)child view的坐標(biāo)范圍內(nèi),那么該child view來處理這個(gè)觸摸事件,即調(diào)用該child view的dispatchTouchEvent。如果該child view是ViewGroup類型,那么繼續(xù)執(zhí)行上面的判斷,并且遍歷子view;如果該child view不是ViewGroup類型,那么直接調(diào)用的是View中的dispatchTouchEvent方法,除非這個(gè)child view的類型覆寫了該方法。我們看看View中的dispatchTouchEvent函數(shù):

View的Touch事件分發(fā)

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

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

/** 
 * 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則繼續(xù)執(zhí)行onTouchEvent(event);如果mOnTouchListener.onTouch(this, event)返回true,則表示該事件被消費(fèi)了,不再傳遞,因此也不會執(zhí)行onTouchEvent(event)。這也驗(yàn)證了我們上文中留下的場景2,當(dāng)onTouch函數(shù)返回true時(shí),點(diǎn)擊按鈕,但我們的點(diǎn)擊事件沒有執(zhí)行。那么我們還是先來看看onTouchEvent(event)函數(shù)到底做了什么吧。

/** 
 * 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();    // 獲取焦點(diǎn) 
          } 
 
          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、點(diǎn)擊事件處理 
              } 
            } 
          } 
 
          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函數(shù)中就是對ACTION_UP、ACTION_DOWN、ACTION_MOVE等幾個(gè)事件進(jìn)行處理,而最重要的就是UP事件了,因?yàn)檫@個(gè)里面包含了對用戶點(diǎn)擊事件的處理,或者是說對于用戶而言相對重要一點(diǎn),因此放在了第一個(gè)case中。在ACTION_UP事件中會判斷該view是否enable、是否clickable、是否獲取到了焦點(diǎn),然后我們看到會通過post方法將一個(gè)PerformClick對象投遞給UI線程,如果投遞失敗則直接調(diào)用performClick函數(shù)執(zhí)行點(diǎn)擊事件。

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

可以看到,其內(nèi)部就是包裝了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; 
 } 

代碼很簡單,主要就是調(diào)用了mOnClickListener.onClick(this);方法,即執(zhí)行用戶通過setOnClickListener設(shè)置進(jìn)來的點(diǎn)擊事件處理Listener。
 
總結(jié)

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

粗略的流程圖如下 :

相信本文所述對大家進(jìn)一步深入掌握Android程序設(shè)計(jì)有一定的借鑒價(jià)值。

相關(guān)文章

最新評論