·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> app软件开发 >> IOS开发 >> AndroidGUI之View事件处理

AndroidGUI之View事件处理

作者:佚名      IOS开发编辑:admin      更新时间:2022-07-23

   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群:JRedu技术交流 (452379712)

 

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