Android View事件分发

Android中的控件大体分为View和ViewGroup两类,而ViewGroup本质也是View(继承自View),本篇文章也主要分析View的事件分发(源码基于Android3.0)

View事件分发:

View的事件传递是从dispatchTouchEvent中开始的,在View中我们找到该方法:

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) {
            return false;
        }

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

在该方法中我们可以看到主要的判断条件:mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&mOnTouchListener.onTouch(this, event)。

mOnTouchListener指的是该view是否设置了onTouchListener:

/**
     * Register a callback to be invoked when a touch event is sent to this view.
     * @param l the touch listener to attach to this view
     */
    public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }

(mViewFlags & ENABLED_MASK) == ENABLED 代表该View是否可用通过setEnable(boolean enable)来改变

mOnTouchListener.onTouch(this, event)顾名思义就是onTouchListener的返回值

可以看出只有这三个条件同时返回满足dispatchTouchEvent方法会返回True,否则就会去执行返回onTouchEvent(event),接下来我们来看一下该方法都做了哪些事:

/**
     * Implement this method to handle touch screen motion events.
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // 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 (!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) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    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
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // 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;
    }

首先是判断了view是否是enable的如果不可用就会返回CLICKABLE或者LONG_CLICKABLE,一般不手动设置enable默认都是true的所以这一块跳过,下面是对mTouchDelegate的一些判断,该值主要是处理增大view的点击区域的问题,如有需要自己查阅相关资料,这里不过多说明,如果不手动设置mTouchDelegate是null的所以也跳过。

接下来是重点((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE),如果view是可点击或可长按的就会进入条件内部,在条件中对MotionEvent的action做了处理,其中在MotionEvent.ACTION_UP分支中我们可以看到performClick()方法的调用,而找到该方法:

/**
     * Call this view's OnClickListener, if it is defined.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

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

        return false;
    }

可以看出这里正式对view有没有设置onClickListener,如果有就调用其onClick方法。

从onTouchEvent的源码中我们可以看出只要进入了((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)判断内部,无论action的值为什么最终该方法都会返回true,由此可以大胆的推断出,ontouch事件中的action触发的前提是上一个action返回的是true。对于这个问题我们可以通过设置view的clickAble为false并为其设置ontouch事件来观察action的触发情况,将出现执行了down之后就不再执行。

至此我们大致就可以弄清楚以下几个问题:

  • 1.view的onTouch方法先于onclick调用
  • 2.如果onTouch中返回true,onTouchEvent方法不会调用onclick事件就不会触发。
  • 3.onTouch方法执行的前提是onTouchListener不为null,并且view的enable为true。如果View是不可用的我们只能通过重写该view的onTouchEvent方法来监听事件。
  • 4.无论怎么做处理(前提view可用,事件没有被父容器拦截)子view的down事件一定会触发

view的方法调用dispatchTouchEvent--->onTouch-->onTouchEvent-->onClick

最后附上View的事件分发步骤图解:

 

 

 

参考资料:

https://blog.youkuaiyun.com/guolin_blog/article/details/9097463

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值