你的位置:首页 > 操作系统

[操作系统]Android GUI之View事件处理


   Android中的事件分为按键事件和触屏事件,本篇文章将分析View是如何处理Touch事件的。在View中定义了许多触屏事件,比如OnClick、OnLongClick等等,这些事件都是由一次Touch中的动作如ACTION_DOWN、ACTION_MOVE、ACTION_UP组成的。

  首先,我们先看一个简单的案例,Activity中只含有一个Button,我们为Button分别绑定了OnClickListener和OnTouchListener,具体代码如下:

btnOk.setOnClickListener(new View.OnClickListener() {  @Override  public void onClick(View v) {    Log.i(TAG,"按钮的点击事件执行了");  }});btnOk.setOnTouchListener(new View.OnTouchListener() {  @Override  public boolean onTouch(View v, MotionEvent event) {    Log.i(TAG,"按钮的Touch事件执行了");    return false;  }});

那么,当我们点击按钮时,到底先执行哪个事件呢?具体执行结果如下:

  很明显,先执行了OnTouch事件,然后才执行了OnClick,这是为什么呢?我们下面跟踪源码,具体查看下整个事件的执行流程。

  当我们点击Button按钮时,首先将要执行的是dispatchTouchEvent方法,至于为什么会执行此方法,我们后面再进行分析。通过源码,我们并没有在Button中找到该方法,顺着Button的继承体系,我们追踪到View,发现该方法存在于View中,具体内容如下:

public boolean dispatchTouchEvent(MotionEvent event) {  // If the event should be handled by accessibility focus first.  if (event.isTargetAccessibilityFocus()) {    // We don't have focus or no virtual descendant has it, do not handle the event.    if (!isAccessibilityFocusedViewOrHost()) {      return false;    }    // We have focus and got the event, then use normal event dispatch.    event.setTargetAccessibilityFocus(false);  }  boolean result = false;  if (mInputEventConsistencyVerifier != null) {    mInputEventConsistencyVerifier.onTouchEvent(event, 0);  }  final int actionMasked = event.getActionMasked();  if (actionMasked == MotionEvent.ACTION_DOWN) {    // Defensive cleanup for new gesture    stopNestedScroll();  }  if (onFilterTouchEventForSecurity(event)) {    //noinspection SimplifiableIfStatement    ListenerInfo li = mListenerInfo;    if (li != null && li.mOnTouchListener != null        && (mViewFlags & ENABLED_MASK) == ENABLED        && li.mOnTouchListener.onTouch(this, event)) {      result = true;    }    if (!result && onTouchEvent(event)) {      result = true;    }  }  if (!result && mInputEventConsistencyVerifier != null) {    mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  }  // Clean up after nested scrolls if this is the end of a gesture;  // also cancel it if we tried an ACTION_DOWN but we didn't want the rest  // of the gesture.  if (actionMasked == MotionEvent.ACTION_UP ||      actionMasked == MotionEvent.ACTION_CANCEL ||      (actionMasked == MotionEvent.ACTION_DOWN && !result)) {    stopNestedScroll();  }  return result;}

仔细分析阅读源码,我们发现了关键的处理部分,具体如下:
if (onFilterTouchEventForSecurity(event)) {    //noinspection SimplifiableIfStatement    ListenerInfo li = mListenerInfo;    if (li != null && li.mOnTouchListener != null        && (mViewFlags & ENABLED_MASK) == ENABLED        && li.mOnTouchListener.onTouch(this, event)) {      result = true;    }    if (!result && onTouchEvent(event)) {      result = true;    }  }

  在此段代码中,涉及到了onTouchEvent方法和onTouchListener,此二者都是View中用于处理Touch事件。很显然,如果View控件设置了onTouchListener监听器,并且当前View控件是可用的,则先执行onTouchListener中的onTouch方法,如果此方法返回了false,则继续执行View的onTouchEvent方法,如果返回true,则到此为止,不在执行onTouchEvent方法。简单验证一下,我们将上面的案例稍微改造下,我们自定义一个MyButton,让其继承Button,并重写onTouchEvent方法,在其中加入一条输入语句,并将MyButton替换掉Button,具体如下:

@Overridepublic boolean onTouchEvent(MotionEvent event) {  Log.i("View事件处理","这是View的onTouchEvent" +      "方法,当前动作为:"+event.getAction());  return super.onTouchEvent(event);}

点击按钮,输出结果为:

 

将onTouchListener中的onTouch方法中的返回值改为true,其结果为:

 

很明显MyButton中的onTouch方法及onClick都没有执行。
从而也可以知道onClick时间肯定和onTouch方法有关系,查看View中的onTouch方法,具体源码如下:
public boolean onTouchEvent(MotionEvent event) {  ……  if (((viewFlags & CLICKABLE) == CLICKABLE ||      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {    switch (event.getAction()) {      case MotionEvent.ACTION_UP:        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {          // take focus if we don't have it already and we should in          // touch mode.          boolean focusTaken = false;          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {            focusTaken = requestFocus();          }          if (prepressed) {            // The button is being released before we actually            // showed it as pressed. Make it show the pressed            // state now (before scheduling the click) to ensure            // the user sees it.            setPressed(true, x, y);          }          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)) {                performClick();              }            }          }          if (mUnsetPressedState == null) {            mUnsetPressedState = new UnsetPressedState();          }          if (prepressed) {            postDelayed(mUnsetPressedState,                ViewConfiguration.getPressedStateDuration());          } else if (!post(mUnsetPressedState)) {            // If the post failed, unpress right now            mUnsetPressedState.run();          }          removeTapCallback();        }        break;      case MotionEvent.ACTION_DOWN:        mHasPerformedLongPress = false;        if (performButtonActionOnTouchDown(event)) {          break;        }        // Walk up the hierarchy to determine if we're inside a scrolling container.        boolean isInScrollingContainer = isInScrollingContainer();        // For views inside a scrolling container, delay the pressed feedback for        // a short period in case this is a scroll.        if (isInScrollingContainer) {          mPrivateFlags |= PFLAG_PREPRESSED;          if (mPendingCheckForTap == null) {            mPendingCheckForTap = new CheckForTap();          }          mPendingCheckForTap.x = event.getX();          mPendingCheckForTap.y = event.getY();          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());        } else {          // Not inside a scrolling container, so show the feedback right away          setPressed(true, x, y);          checkForLongClick(0);        }        break;      case MotionEvent.ACTION_CANCEL:        setPressed(false);        removeTapCallback();        removeLongPressCallback();        break;      case MotionEvent.ACTION_MOVE:        drawableHotspotChanged(x, y);        // Be lenient about moving outside of buttons        if (!pointInView(x, y, mTouchSlop)) {          // Outside button          removeTapCallback();          if ((mPrivateFlags & PFLAG_PRESSED) != 0) {            // Remove any future long press/tap checks            removeLongPressCallback();            setPressed(false);          }        }        break;    }    return true;  }  return false;}

在case MotionEvent.ACTION_UP: 的分支中我们发现了performClick方法,该方法就是单击事件处理方法,可查看源码如下:
public boolean performClick() {  final boolean result;  final ListenerInfo li = mListenerInfo;  if (li != null && li.mOnClickListener != null) {    playSoundEffect(SoundEffectConstants.CLICK);    li.mOnClickListener.onClick(this);    result = true;  } else {    result = false;  }  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  return result;}

  在这里很明显可以看到,最终调用了onClickListener监听器。我们可以总结一下,在View的Touch事件处理中,首先会调用View中的dispatchTouchEvent方法,disptchTouchEvent将事件传递给onTouchListener和onTouchEvent方法进行处理,其中onTouchListener要优先于onTouchEvent,如果onTouchListener的返回值为true则不再执行onTouchEvent。在onTouchEvent方法中,根据Touch的动作分别处理相关的事件,如onClick、onLongClick等,最终执行完成后返回dispatchTouchEvent方法。

 

  想要了解更多内容的小伙伴,可以点击查看源码,亲自运行测试。

  疑问咨询或技术交流,请加入官方QQ群:JRedu技术交流 (452379712)

 

作者:杰瑞教育
出处:http://www.cnblogs.com/jerehedu/ 
本文版权归烟台杰瑞教育科技有限公司和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。