您的位置:首页 > 产品设计 > UI/UE

Android GUI之View事件处理

2015-10-08 14:29 573 查看
   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,具体如下:

@Override
public 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群:

(452379712)

作者:杰瑞教育

出处:http://www.cnblogs.com/jerehedu/

本文版权归烟台杰瑞教育科技有限公司和CSDN共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: