Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung des Android Touch-Ereignisverteilungsprozesses

Detaillierte Erläuterung des Android Touch-Ereignisverteilungsprozesses

高洛峰
高洛峰Original
2017-01-16 16:55:521476Durchsuche

Dieser Artikel beschreibt den Android Touch-Ereignisverteilungsprozess anhand von Beispielen, was für ein tiefgreifendes Verständnis und die Beherrschung der Android-Programmierung sehr hilfreich ist. Die spezifische Analyse lautet wie folgt:

Beginnen Sie zunächst mit einem einfachen Beispiel:

Sehen Sie sich zunächst ein Beispiel wie unten gezeigt an:

Android Touch事件分发过程详解

Layout-Datei:

<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-Datei:

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

Wenn der Benutzer auf die Schaltfläche klickt, wird das folgende Protokoll ausgegeben:

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}

Wir können Stellen Sie sicher, dass die Aktivität zuerst die Methode „dispatchTouchEvent“ ausführt, dann die Methode „onTouch“ ausführen, dann „dispatchTouchEvent -> onTouch“ und schließlich das Klickereignis der Schaltfläche ausführen. Wir haben hier möglicherweise eine Frage: Warum werden „dispatchTouchEvent“ und „onTouch“ zweimal ausgeführt, onClick jedoch nur einmal? Warum unterscheiden sich die Aktionen der beiden Touch-Ereignisse? Was stellen Aktion 0 und Aktion 1 dar?

Freunde, die onTouchEvent überschrieben haben, wissen, dass wir im Allgemeinen zentrale Berührungsereignisse in diesem Methodenkörper verarbeiten, einschließlich ACTION_DOWN, ACTION_MOVE, ACTION_UP usw. In unserem obigen Beispiel gibt es jedoch keine Bewegung, sondern nur Einfach drücken und anheben. Daher sind unsere Berührungsereignisse nur Drücken und Heben, es gibt also 2 Berührungsereignisse und die Aktionen sind 0 bzw. 1. Werfen wir einen Blick auf einige Variablendefinitionen in 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;  // 取消 
 // 代码省略 
}

Wie Sie sehen können, ist das Ereignis, das die Presse darstellt, 0 und das Ereignis, das angehoben wird, ist 1, was ebenfalls bestätigt, was wir oben gesagt haben.

Betrachten Sie zwei weitere Szenen:

1 Wir klicken auf den Bereich außerhalb der Schaltfläche und das Ausgabeprotokoll lautet wie folgt:

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

2. Wir geben true zurück In der onTouch-Funktion lautet das Ausgabeprotokoll wie folgt:

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

Warum sind die beiden oben genannten Szenarien so? Lesen wir weiter.

Android Touch-Ereignisverteilung

Was ist also der gesamte Ereignisverteilungsprozess?

Um es einfach auszudrücken: Wenn der Benutzer den Telefonbildschirm berührt, wird eine Berührungsnachricht generiert. Schließlich wird diese Berührungsnachricht an den InputHandler von ViewRoot gesendet (beim Betrachten des Quellcodes von 4.2). Die Klasse wurde in ViewRootImpl geändert. ViewRoot ist ein GUI-Verwaltungssystem. Gemäß der Definition von ViewRoot handelt es sich nicht um einen Ansichtstyp, sondern um einen Handler. InputHandler ist ein Schnittstellentyp, der zur Verarbeitung von Ereignissen vom Typ KeyEvent und TouchEvent verwendet wird. Schauen wir uns den Quellcode an:

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); 
      // 代码省略   
    } 
  } 
  // 代码省略 
}

Nach Nebelebenen, unabhängig davon, ob die mView in Code 7 DecorView oder das Stammverzeichnis ist Ansicht der Nicht-Fensterschnittstelle, ihr Kern ist ViewGroup, das heißt, Berührungsereignisse werden schließlich von der Root-Ansicht ViewGroup verteilt! ! !
Wir werden Aktivität als Beispiel nehmen, um diesen Prozess zu analysieren. Wir wissen, dass die Implementierungsklasse dieses Fensters eine Ansicht vom Typ DecorView ist Wie auf dem Mobiltelefon zu sehen ist, ist diese DecorView eine Unterklasse von „dispatchTouchEvent“ von FrameLayout. Schauen wir uns den Quellcode an und geben Sie die „dispatchTouchEvent“-Funktion von Activity ein:

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() { 
 }

Wie Sie können Sehen Sie, wenn es sich bei dem Ereignis um ein Presseereignis handelt, wird es in die Funktion onUserInteraction() eingegeben. Diese Funktion ist leer und wir werden sie vorerst ignorieren. Lesen Sie weiter und stellen Sie fest, dass die Verteilung von Berührungsereignissen die Funktion getWindow().superDispatchTouchEvent(ev) aufruft. Der von getWindow() erhaltene Instanztyp ist der PhoneWindow-Typ. Sie können die folgende Methode in Ihrer Activity-Klasse verwenden, um anzuzeigen, was getWindow ist () erhält. Geben Sie ein:

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

Ausgabe:

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

OK, schauen wir uns ohne weiteres die superDispatchTouchEvent-Funktion in PhoneWindow an.

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

Nun, die superDispatchTouchEvent(event)-Funktion von mDecor wird aufgerufen. Dieses mDecor ist der oben erwähnte DecorView-Typ, der eine ViewGroup der obersten Ebene aller Inhalte der Aktivität ist, die wir sehen ist der Wurzelknoten des gesamten ViewTree. Schauen Sie sich einfach die Aussage an.

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

DecorView

Dann lass mich weiter sehen, was DecorView ist.

    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&#39;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);
    }
// 代码省略
}

Wie Sie sehen, erbt DecorView von FrameLayout. Die Verteilung und Verarbeitung von Touch-Ereignissen (dispatchTouchEvent) wird an die Superklasse übergeben, die von FrameLayout verwaltet wird Verfolgen Sie zur entsprechenden Implementierung weiterhin die übergeordnete Klasse von FrameLayout, nämlich ViewGroup. Wir haben die Implementierung von „dispatchTouchEvent“ gesehen. Schauen wir uns also zunächst an, wie ViewGroup (Android 2.3-Quellcode) Ereignisse verteilt.

Touch-Event-Verteilung von ViewGroup

/**
 * {@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&#39;re disallowing intercept or if we&#39;re allowing and we didn&#39;t
    // intercept
    if (disallowIntercept || !onInterceptTouchEvent(ev))     // 1、是否禁用拦截、是否拦截事件
      // reset this event&#39;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&#39;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&#39;t get handled, try the next view.
            // Don&#39;t reset the event&#39;s location, it&#39;s not
            // necessary here.
          }
        }
      }
    }
  }
  
  boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
      (action == MotionEvent.ACTION_CANCEL);
  
  if (isUpOrCancel) {
    // Note, we&#39;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&#39;t an ACTION_DOWN, dispatch it to our target if
  // we have one.
  final View target = mMotionTarget;
  if (target == null) {
    // We don&#39;t have a target, this means we&#39;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&#39;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&#39;t handle ACTION_CANCEL. not much we can do
      // but they should have.
    }
    // clear the target
    mMotionTarget = null;
    // Don&#39;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&#39;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&#39;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&#39;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&#39;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程序设计有一定的借鉴价值。

更多Android Touch事件分发过程详解相关文章请关注PHP中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn