onTouch,onClick冲突详解

来源:互联网 发布:现场网络直播 编辑:程序博客网 时间:2024/06/18 11:41

onTouch,onClick冲突详解

       ONE Goal,ONE Passion!

概述:

  • ViewGroup和View的分发.

    http://blog.csdn.net/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.csdn.net/fengltxx/article/details/49330343
0 0
原创粉丝点击