关于view的事件机制

其实一直不太理解安卓的事件处理机制,不过今天看到一位大神写的突然有点明白了,文章始于一个问题:touch和click的关系,我们都知道,当我们触摸一个view的时候都会调用改view的touch事件(这个调用是100%的)但是不一定会调用view的click事件,因为有的view是不支持clickable,比如imageview,当然我们可以设置这个imageview可以点击,那么这个view是有一定的可能性调用onCLick事件,下面我们通过一个demo来说明这个问题的原因,demo是一个activity,当中只有一个button,我们对其设置onclick和ontouch事件,注意,onntouch事件默认是返回flase的。

button.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Log.v("xxxx","click");
			}
		});
		
button.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				Log.v("xxxx","touch"+event.getAction());
				return flase;
			}
		});

然后我们点击这个button,查看log如下

我们会发现touch事件和click事件都执行了,并且touch事件的执行优于onclick事件,如果我们把onTouch事件返回true,那么我们继续查看log


此时onclick事件却没有执行,此时我们就会想到这是为什么了?其实我们可以这么理解,比如一份食物(代表该触摸事件),会经过1个人传递才回传到我的手上来,那么如果第一个人吃了食物,并且告诉我这个食物被吃了(return true),那么我就没办法接受这个食物(因为该事件不存在了),如果这个人没吃食物,就会把食物传递给我(return false),那么此时我就可以接受这个食物,并且吃掉它(onclick方法执行)

那么我们从源码的角度来看这个完整的事件过程是如何进行的,我们要记住任何view的触摸事件都会首先调用其dispatchTouchEvent方法,那我们就从这个方法开始

我们查看button没有dispatchTouchEvent方法,我们就去找其父类和其父类的父类也就是view

public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

重点就在if(noFilterTouchEventForSecurity(Event))里面,我们会发现第一个循环语句有3个判断,我们先来看第一个判断,也就是onTouchListener!=null 这个是在哪里申明的啊?我们继续查看源代码发现

 public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }
也就是我们在代码里面setOnTouchListener的时候申明的,第二个条件因为按钮都是默认都是enable的所有是永远都是true,第三个条件就是调用onTouchListener.onTouch方法这个方法是否为true取决于我们代码中onTouch事件的返回值,如果我们return true,那么这个if是成立的就会跳出整个循环,如果return false,那么就会执行下一个if语句

在这个onTouch方法返回的时候,就已经执行了,所以我们的onTouch方法是事件执行的一个关卡,为什么要称呼其为关卡,因为它的返回值决定了是否执行onclick方法,

我们接下来查看onTouchEvn

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

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
            }
            // 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 & PREPRESSED) != 0;
                    if ((mPrivateFlags & 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.
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                       }

                        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 |= PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    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 & PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }
我们只需要关注这个if就行了
 if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

进而会执行

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

        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }

这个方法,在这个方法里面我们会发现onclick方法就是在这里执行的,那么就一切明了了,onTouch方法的返回值决定了是否执行onClick方法。

View的触摸都是从dispatchTouchEvent方法开始执行的,

但是这里还有另一个问题,就是如果OnTouch方法中,我们在Down方法的时候return false 后那么接下里的action就不会被接收执行,但是我们刚刚return false的时候

还是执行了,这是为什么?因为在onTouchEvent里面帮助我们return true了,所以都是可以执行的。View的机制我也明白具体的一点点,各位看官,我写的很差,大多数都是从一位大神那里copy过来的,详情请点击下面的链接

http://blog.csdn.net/guolin_blog/article/details/9097463,再次感谢这位大神!



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值