onTouch,onClick冲突详解

本文深入解析Android中View的触控事件流程,包括onTouchEvent和onClick事件的触发机制及冲突解决办法。通过源码分析,揭示了触控事件如何在DOWN、MOVE、UP阶段流转,并解释了长按与点击事件之间的相互影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

onTouch,onClick冲突详解

       ONE Goal,ONE Passion!

概述:

  • ViewGroup和View的分发.

    http://blog.youkuaiyun.com/fengltxx/article/details/49330343
    
  • 介绍一个OnTouchEvent中事件流程


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:

            downX = (int) event.getX();

            System.out.println("---ACTION_DOWN--");
            break;

        case MotionEvent.ACTION_MOVE:
            System.out.println("---ACTION_MOVE--");

            break;
        case MotionEvent.ACTION_UP:

            upX = (int) event.getX();
            System.out.println("---ACTION_UP--");

            if (Math.abs(downX - upX) > 30) {

                return true;
            }

            break;
        }
        return super.onTouchEvent(event);
        }

咦…….为什么要返回一个super.onTouchEvent(event).

注意:系统想接收到此事件.

  • 1.return super.onTouchEvent(event);
  • 2.调用super.onTouchEvent(event)即:(将这句代码放在第onTouchEvent(MotionEvent event)方法);

开始我们的测试

在ACTION_DOWN中return true.



    case MotionEvent.ACTION_DOWN:

            downX = (int) event.getX();

            System.out.println("---ACTION_DOWN--");
            return true;

当我们点击(down)view并且抬起(up)时.onclick就不能被回调.为什么呢?因为我们没有将down事件传递给系统.仅仅把up事件传递了. 为什么不传递down事件就不能执行onclick呢?going..

来看看super.onTouchEvent(event)到底做了什么?


     public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn'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)) {
            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);
                       }

                        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();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // 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;
    }

我们先找到onClick在哪个地方被回调.嘎嘎 …找到了在UP事件中: performClick();

       public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);  // 是不是很熟悉.这里就是onClick的回调
            return true;
        }

        return false;
    }

哎呦!想要执行performClick();方法好像要经过2层判断.首先我们先看第一层判断.

第一层判断

找到第28行.

    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)

系统判断 mPrivateFlags & PFLAG_PRESSED 和 prepressed的值是否有一个为true.这两个值和什么有关呢?
看DOWN中的第90行:

     mPrivateFlags |= PFLAG_PREPRESSED; 

做完这个处理第28行判断就为true了.
噢.原来没有DOWN事件.根本没办法执行UP中performClick()中代码.所以我们的onClick事件不能被回调.

如果我们在自定义的View中DOWN中返回Super.onTouchEvent()就可以接收onClick事件了吗?.NO !
别忘了我们还有第二层判断:
找到第44行

 if (!mHasPerformedLongPress)   //判断是否已经执行长按

哎呦喂!这个mHasPerformedLongPress是什么鬼? 想一下:如果这个值为false那么不就可以突破第二层判断了嘛!答案是–YES. 可是会不会在什么时候系统将mHasPerformedLongPress设置为True呢? 所以我们一路Ctrl+F.(mHasPerformedLongPress = true;)下一步.终于找到了.

  class CheckForLongPress implements Runnable {

        private int mOriginalWindowAttachCount;

        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                    mHasPerformedLongPress = true;
                }
            }
        }

系统在CheckForLongPress代码块中将mHasPerformedLongPress = true.谁执行了CheckForLongPress呢?
继续找:


       private void checkForLongClick(int delayOffset) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

bingo!checkForLongClick方法中执行了CheckForLongPress.又是谁执行了checkForLongClick呢?可以看到有很多地方都执行了这个方法.我们找到在CheckForTap方法中执行了checkForLongClick.


        //检查是否是点击事件
       private final class CheckForTap implements Runnable {
        public void run() {
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true);
        //在点击事件中检查是否是长按事件
            checkForLongClick(ViewConfiguration.getTapTimeout());
        }
    }

继续找CheckForTap.看看到底在哪个地方执行.哇哦!

找到第91-94.在DOWN中执行有这段代码:

  if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
    // 延迟 180ms后执行mPendingCheckForTap代码块.目的就是检查事件是点击DOWN还是长按事件
     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

最后我们看到.如果代码执行到了CheckForLongPress方法中在将 mHasPerformedLongPress = true;之前还有一个判断if (performLongClick()).只有当performLongClick() = true时.才会将mHasPerformedLongPress赋值为true.

等一下: performLongClick()又是什么鬼从字面意思可以看出.就是执行长点击.我们可以重写这个方法.通过return true还是false来控制mHasPerformedLongPress是否可以被赋值为true.

我去…..终于搞完了.

总结下来就是:

  • 1.当手指点下,在onTouchEvent中如果调用了super.onTouchEvent将事件传递给了系统.系统会先执行Action_DOWN.
  • 2.在DOWN中,首先将mHasPerformedLongPress = false.然后调用postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());去检查是否是敲击事件.
  • 3.在mPendingCheckForTap代码块中会去延时调用checkForLongClick().去检查是否是长点击事件.
  • 4.在checkForLongClick()中再去延时调用CheckForLongPress(),去检查是否是长按事件
  • 5.在CheckForLongPress()中会根据performLongClick()的返回值来决定是否将mHasPerformedLongPress = true.

每次检查都是延时操作.如果在还没有检查完,就抬起的话.由于在down是已经将mHasPerformedLongPress= false.所以onclick事件是可以执行回调的.

可以看出:

  • 1.长按事件是在DOWN事件中判断的.
  • 2.点击事件也要经过DOWN事件,而且和长按事件返回值有关.如果返回true(可以理解为将事件消费了)—执行长按就不能执行onclick.返回false同样可以执行onClick

注: ViewGroup和View的分发机制详解.

http://blog.youkuaiyun.com/fengltxx/article/details/49330343
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值