Home > Java > javaTutorial > Detailed explanation of Android Touch event distribution process

Detailed explanation of Android Touch event distribution process

高洛峰
Release: 2017-01-16 16:55:52
Original
1508 people have browsed it

This article describes the Android Touch event distribution process in the form of examples, which is very helpful for in-depth understanding and mastery of Android programming. The specific analysis is as follows:

First, start with a simple example:

First look at an example as shown below:

Android Touch事件分发过程详解

Layout file :

<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>
Copy after login

MainActivity file:

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); 
  } 
}
Copy after login

When the user clicks the button, the following Log will be output:

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}
Copy after login

We can see that the dispatchTouchEvent method in the Activity is first executed, and then executed onTouch method, then dispatchTouchEvent --> onTouch, and finally the click event of the button is executed. We may have a question here, why are dispatchTouchEvent and onTouch executed twice, but onClick only executed once? Why are the actions of the two Touch events different? What do action 0 and action 1 represent?

Friends who have overwritten onTouchEvent know that generally speaking, we will handle centralized touch type events in this method body, including ACTION_DOWN, ACTION_MOVE, ACTION_UP, etc. However, in our example above, there is no movement, just simple Press and lift. Therefore, our touch events are only press and lift, so there are 2 touch events, and the actions are 0 and 1 respectively. Let’s take a look at some variable definitions 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;  // 取消 
 // 代码省略 
}
Copy after login

As you can see, the event representing the press is 0, and the event that is lifted is 1, which also confirms what we said above.

Looking at two other scenarios:

1. We click the area outside the button, and the output Log is as follows:

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

2. We return true in the onTouch function, and output The Log is as follows:

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
Copy after login

Why are the above two scenarios like this? Let’s continue reading.

Android Touch event distribution

So what is the entire event distribution process?

To put it simply, when the user touches the phone screen, a touch message will be generated. Finally, this touch message will be sent to the InputHandler of ViewRoot (when looking at the source code of 4.2, this class was changed to ViewRootImpl). ViewRoot is a GUI management system. The bridge with the GUI rendering system, according to the definition of ViewRoot, it is found that it is not a View type, but a Handler. InputHandler is an interface type used to handle KeyEvent and TouchEvent type events. Let’s take a look at the source code:

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

After layers of fog, no matter whether the mView in code 7 is DecorView or the root view of a non-window interface, it The essence is ViewGroup, that is, touch events are eventually distributed by the root view ViewGroup! ! !
We will take Activity as an example to analyze this process. We know that the displayed Activity has a top-level window. The implementation class of this window is PhoneWindow. The content area in PhoneWindow is a DecorView type View. This View is our As seen on the mobile phone, this DecorView is a subclass of FrameLayout. Activity's dispatchTouchEvent actually calls PhoneWindow's dispatchTouchEvent. Let's take a look at the source code and enter Activity's dispatchTouchEvent function:

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

You can see If the event is a press event, it will enter the onUserInteraction() function. This function is empty and we will ignore it for now. Continue reading and find that the distribution of touch events calls the getWindow().superDispatchTouchEvent(ev) function. The type of instance obtained by getWindow() is the PhoneWindow type. You can use the following method in your Activity class to view what getWindow() obtains. Type:

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

Output:

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

OK, without further ado, let’s continue looking at the superDispatchTouchEvent function in PhoneWindow.

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

Well, the superDispatchTouchEvent(event) function of mDecor is called. This mDecor is the DecorView type we mentioned above, which is a top-level ViewGroup of all the content on the Activity we see, that is, the entire ViewTree the root node. Just look at its statement.

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

DecorView

Then let me continue to see what DecorView is.

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

It can be seen that DecorView inherits from FrameLayout. The distribution and processing of touch events (dispatchTouchEvent) are handed over to the super class, which is handled by FrameLayout. We do not see the corresponding in FrameLayout. Implementation, then continue to track the parent class of FrameLayout, namely ViewGroup, we have seen the implementation of dispatchTouchEvent, then let's first look at how ViewGroup (Android 2.3 source code) distributes events.

Touch event distribution of 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);
}
Copy after login


这个函数代码比较长,我们只看上文中标注的几个关键点。首先在代码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);
}
Copy after login


该函数中,首先判断该事件是否符合安全策略,然后判断该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;
}
Copy after login

如果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;
}
Copy after login

我们看到,在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);
}
Copy after login

我们看看PerformClick类吧。

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

可以看到,其内部就是包装了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;
 }
Copy after login

  

代码很简单,主要就是调用了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中文网!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template