View-事件分发

View 的事件分发处理各种滑动冲突,复杂布局中事件处理的基础,这里对View的事件分发做一个简单的整理分析。

View的事件分发直接对应用户的操作就是对view的点击处理,就是对MotionEvent这个对象进行分析。

1.ViewGroup事件分发

我们首先分析ViewGroup的事件分发,其中我们需要了解其中最为重要的三个方法。

  • dispatchTouchEvent()

用于分发接收到的事件,如果当前View可以接收到事件这个方法一定会被调用,返回true 表示当前view处理事件,false不处理,事件将继续传递给子view

  • onInterceptTouchEvent()

在dispatchTouchEvent()内部调用,用于拦截判断是否拦截某个事件,一但拦截了事件在同一个事件序列中此方案讲不再被调用。这里的同一事件序列指的是用户操作屏幕后产生的down,move,up一套完整的流程。

  • onTouchEvent()

在dispatchTouchEvent()内部调用,用于处理点击事件。返回true表示消耗当前事件,false表示不消耗,并且在同一事件序列下无法在接收事件。

他们三者的关系可以用一段伪代码表示

public boolean dispatchTouchEvent(Motion e){
     boolean result=false;
     if(onInterceptTouchEvent(e)){
     //如果当前View截获事件,那么事件就会由当前View处理,即调用onTouchEvent()
        result=onTouchEvent(e);
     }else{
        //如果不截获那么交给其子View来分发
        result=child.dispatchTouchEvent(e);
     }
     return result;
}
总结

1.一个点击事件产生后当根ViewGroup接收到事件,他的dispatchTouchEvent就会被调用,当onInterceptTouchEvent方法返回true的时候,表示当前的view要拦截这个事件,那么后续的事件就会交给当前的View去处理。就会接着调用onTouchEvent方法。onInterceptTouchEvent返回为false的时候调用子view的dispatchTouchEvent方法直到这个事件被处理。

1.View事件分发

这里的view是不含ViewGroup的。我们看下View的dispatchTouchEvent方法源码

  /**
     * 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 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)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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;
    }

view是没有子元素的,所以他的事件没法继续往下传递。其中有段代码比较重要

  //处理点击事件
  if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
          
            //先判断有没有设置mOnTouchListener,并判断mOnTouchListener.onTouch的返回值
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

			//如果mOnTouchListener的onTouch方法返回true,则onTouchEvent就不会被调用
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

我们在看一下onTouchEvent方法

  /**
     * Implement this method to handle touch screen motion events.
     * <p>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     * {@link #performClick()}. This will ensure consistent system behavior,
     * including:
     * <ul>
     * <li>obeying click sound preferences
     * <li>dispatching OnClickListener calls
     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
     * accessibility features are enabled
     * </ul>
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
  public boolean onTouchEvent(MotionEvent event) {
  	.....

	 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            //不可用的View照样会消耗点击事件
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

		......

		 case MotionEvent.ACTION_UP:
		  if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 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)) {
                                    performClickInternal();
                                }
                            }
                        }
  }
总结

1.View会先判断有没有设置onTouchListener,如果mOnTouchListener的onTouch返回true则onTouchEvent就不会被调用,说明onTouchListener的优先级会高于onTouchEvent。

2.不可用的view依然会消耗点击事件

3.如果view设置有TouchDelegate,会执行TouchDelegate的touch方法

4.ACTION_UP中会触发PerformClick,如果view设置了onClickListener,PerformClick会调用onclick方法。因此知道OnTouchListener的优先级高于OnClick。OnClick是在onTouchListener没被设置的情况下onTouchEvent中触发的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值