Maison > Java > javaDidacticiel > Explication détaillée du processus de distribution d'événements Android Touch

Explication détaillée du processus de distribution d'événements Android Touch

高洛峰
Libérer: 2017-01-16 16:55:52
original
1510 Les gens l'ont consulté

Cet article décrit le processus de distribution d'événements Android Touch sous forme d'exemples, ce qui est très utile pour une compréhension et une maîtrise approfondies de la programmation Android. L'analyse spécifique est la suivante :

Tout d'abord, commencez par un exemple simple :

Regardez d'abord un exemple comme indiqué ci-dessous :

Android Touch事件分发过程详解

Fichier de mise en page :

<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>
Copier après la connexion

Fichier 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); 
  } 
}
Copier après la connexion

Lorsque l'utilisateur clique sur le bouton, le journal suivant sera affiché :

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}
Copier après la connexion

Nous pouvons voyez que l'activité est exécutée d'abord par la méthode dispatchTouchEvent, puis exécutez la méthode onTouch, puis dispatchTouchEvent --> onTouch, et enfin exécutez l'événement click du bouton. Nous pouvons avoir une question ici, pourquoi dispatchTouchEvent et onTouch sont-ils exécutés deux fois, mais onClick n'est-il exécuté qu'une seule fois ? Pourquoi les actions des deux événements Touch sont-elles différentes ? Que représentent l'action 0 et l'action 1 ?

Les amis qui ont remplacé onTouchEvent savent que de manière générale, nous gérerons les événements de type tactile centralisés dans ce corps de méthode, notamment ACTION_DOWN, ACTION_MOVE, ACTION_UP, etc. Cependant, dans notre exemple ci-dessus, il n'y a pas de mouvement, juste simple Appuyez et soulevez. Par conséquent, nos événements tactiles sont uniquement une pression et un levage, il y a donc 2 événements tactiles et les actions sont respectivement 0 et 1. Jetons un coup d'œil à quelques définitions de variables dans 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;  // 取消 
 // 代码省略 
}
Copier après la connexion

Comme vous pouvez le constater, l'événement représentant la presse est 0, et l'événement qui est levé est 1, ce qui confirme également ce que nous avons dit plus haut.

En regardant deux autres scènes :

1. Nous cliquons sur la zone à l'extérieur du bouton, et le journal de sortie est le suivant :

08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent08-31  
03:04:45.512: D/(1560): ### activity dispatchTouchEvent
Copier après la connexion

2. dans la fonction onTouch, le journal de sortie est le suivant :

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
Copier après la connexion

Pourquoi les deux scénarios ci-dessus sont-ils comme celui-ci ? Continuons la lecture.

Distribution d'événements Android Touch

Alors, quel est l'ensemble du processus de distribution d'événements ?

Pour faire simple, lorsque l'utilisateur touche l'écran du téléphone, un message tactile sera généré. Enfin, ce message tactile sera envoyé au InputHandler de ViewRoot (en regardant le code source de 4.2, ceci. la classe a été modifiée en ViewRootImpl). ViewRoot est un système de gestion GUI. Le pont avec le système de rendu GUI, selon la définition de ViewRoot, il s'avère qu'il ne s'agit pas d'un type View, mais d'un Handler. InputHandler est un type d'interface utilisé pour gérer les événements de type KeyEvent et TouchEvent. Jetons un coup d'œil au code source :

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); 
      // 代码省略   
    } 
  } 
  // 代码省略 
}
Copier après la connexion

Après des couches de brouillard, peu importe que le mView dans le code 7 soit DecorView ou la racine. vue de l'interface sans fenêtre, son essence est ViewGroup, c'est-à-dire que les événements tactiles sont finalement distribués par la vue racine ViewGroup ! ! !
Nous prendrons Activity comme exemple pour analyser ce processus. Nous savons que l'activité affichée a une fenêtre de niveau supérieur. La classe d'implémentation de cette fenêtre est PhoneWindow. La zone de contenu de PhoneWindow est une vue de type DecorView. est notre Comme on le voit sur le téléphone mobile, ce DecorView est une sous-classe de dispatchTouchEvent d'Activity appelle en fait dispatchTouchEvent de PhoneWindow. Jetons un coup d'œil au code source et entrons dans la fonction dispatchTouchEvent d'Activity :

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() { 
 }
Copier après la connexion

Vous pouvez voir. que si l'événement est un événement de presse, il entrera dans la fonction onUserInteraction(). Cette fonction est vide et nous l'ignorerons pour l'instant. Continuez à lire et constatez que la distribution des événements tactiles appelle la fonction getWindow().superDispatchTouchEvent(ev). Le type d'instance obtenu par getWindow() est le type PhoneWindow. Vous pouvez utiliser la méthode suivante dans votre classe Activity pour voir ce que getWindow. () obtient. Tapez :

Log.d("", "### Activiti中getWindow()获取的类型是 : " + this.getWindow()) ;
Copier après la connexion

Sortie :

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

OK, sans plus tarder, continuons à regarder la fonction superDispatchTouchEvent dans PhoneWindow.

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
}
Copier après la connexion

Eh bien, la fonction superDispatchTouchEvent(event) de mDecor est appelée. Ce mDecor est le type DecorView que nous avons mentionné ci-dessus, qui est un ViewGroup de niveau supérieur de tout le contenu de l'activité que nous voyons, cela. est le nœud racine de l'ensemble du ViewTree. Il suffit de regarder sa déclaration.

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
Copier après la connexion

DecorView

Alors laissez-moi continuer à voir ce qu'est 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&#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);
    }
// 代码省略
}
Copier après la connexion

Comme vous pouvez le voir, DecorView hérite de FrameLayout. La distribution et le traitement des événements tactiles (dispatchTouchEvent) sont confiés à la super classe, qui est gérée par FrameLayout. .Pour l'implémentation correspondante, continuez à suivre la classe parent de FrameLayout, à savoir ViewGroup. Nous avons vu l'implémentation de dispatchTouchEvent, regardons donc d'abord comment ViewGroup (code source Android 2.3) distribue les événements.

Distribution des événements tactiles de 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);
}
Copier après la connexion


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


该函数中,首先判断该事件是否符合安全策略,然后判断该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;
}
Copier après la connexion

如果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;
}
Copier après la connexion

我们看到,在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);
}
Copier après la connexion

我们看看PerformClick类吧。

private final class PerformClick implements Runnable {
  public void run() {
    performClick();
  }
}
Copier après la connexion

可以看到,其内部就是包装了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;
 }
Copier après la connexion

  

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

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal