Android View事件分发机制

背景

在开发中,我们经常需要自定义android组件,而事件的处理是最重要的部分之一,当手指按下,拖动和释放,都经历了什么事件的处理,会达到怎样的效果,当滑动冲突时,我们需要怎么去解决问题,通过对事件分发的了解,我想,对于上面的问题,你都能迎刃而解。在android中主要两种组件,一是原始的View,例如:TextView,Button …… ,一是继承View的ViewGroup,例如:RelativeLayout,LinearLayout …… ,两者还是有些区别的。

View的事件分发

首先来看View的事件分发机制。

1.1 view事件分发图。
view事件分发图

1.2 分发过程。
① 在手机屏幕上发生MotionEvent.DOWN 事件时,即手机在屏幕上按下时,底层将触摸事件传递给View的boolean dispatchTouchEvent(MotionEvent ev)方法,所有的事件都在这个方法里面开始进行分发处理。

② dispatchTouchEvent()有boolean类型的返回值,返回 true 则表示在此view中直接消费掉该事件,可以理解为,直接把事件“吃”了,谁也不给,就自己享用,所以它的下一级View是不会收到事件的(PS:此步骤具体运用是发生在ViewGroup中)。

③ dispatchTouchEvent()分发时,先判断该View是否有设置OnTouchListener监听器,如有,则回调OnTouchListener事件的boolean onTouch()方法,该方法默认是返回false,表示不消费该事件,后面手指释放若有设置OnClickListener监听器,则会调用OnClickListener的onClick( );如果onTouch()返回了true,则表示消费该事件,那么上面提到的onClick()就得不到执行。

④ 在分发完onTouch()事件之后,会调用View的onTouchEvent()方法。

1.3 附上view的dispatchTouchEvent()部分源码,API 25

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();

        if (onFilterTouchEventForSecurity(event)) {
            //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;
            }
        }
        return result;
    }

这里省略了一些次要代码:
■ 首先声明了布尔型 result = false 变量,该值是dispathTouchEvent()的返回值;

■ 然后判断mInputEventConsistencyVerifier不为空则调用onTouchEvent()方法;

■ 往下看,根据onFilterTouchEventForSecurity()的返回值来执行里面的代码,这个方法返回该事件是否应该派遣,跟底层有关,一般为true;

■ 看里面的代码,有个ListenerInfo对象li,当li != null , li.mOnTouchListener != null , (mViewFlags & ENABLED_MASK) == ENABLED , li.mOnTouchListener.onTouch(this, event) 都为true时,result = true, 看到li.mOnTouchListener.onTouch(this, event))这个方法,这是就是View的OnTouchListener()监听事件的返回值,当实现该监听方法,并且返回true时,上面那段代码就会将result赋值为true;

■ 最后,返回了result,而dispatchTouchEvent()是在dispatchPointerEvent()里面调用的,返回true则表示当前view直接消费该事件 ,false则会继续分发。

ViewGroup事件分发

再来看看ViewGroup的事件分发:

2.1 ViewGroup事件分发图。
viewGroup事件分发图

2.2 分发过程。
① 在手机屏幕上发生MotionEvent.DOWN 事件时,即手机在屏幕上按下时,底层将触摸事件传递给顶层的View,即继承的ViewGroup的布局Layout,然后事件也是传递到boolean dispatchTouchEvent(MotionEvent ev)方法,所有的事件都在这个方法里面开始进行分发处理;

② dispatchTouchEvent()有boolean类型的返回值,返回 true 则表示在此view中直接消费掉该事件,可以理解为,直接把事件“吃”了,谁也不给,就自己享用,所以它的子View是不会收到事件的;

③ 在dispatchTouchEvent()的分发过程中,首先会调用boolean onInterceptTouchEvent(MotionEvent ev)方法,根据返回值来确定是否拦截事件,即不往下传,返回true则表示拦截该事件,然后直接调用该view的boolean onTouchEvent(MotionEvent event)方法;这个拦截器onInterceptTouchEvent()经常用于滑动冲突的处理。

④ 若在上面的onInterceptTouchEvent()返回false时,即不拦截事件,那么事件将会分发给下一级子View,在源码中可以看到,调用了child.dispatchTouchEvent( )继续进行往下分发;

⑤ 若子View是ViewGroup,则继续走上面的流程;若子view是View,则走View的分发流程,以此递归实现view的分发机制,实现用户与机器的交互。

2.3 附上ViewGroup的dispatchTouchEvent()部分源码 API 25

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

        boolean handled = false;

        if (onFilterTouchEventForSecurity(ev)) {
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action);
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            if (!canceled && !intercepted) {
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    if (newTouchTarget == null && childrenCount != 0) {
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                ......
                                break;
                            }
                        }
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                while (target != null) {
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                    }
                }
            }
        }
        return handled;
    }

这里省略了一些次要代码:

■ 首先判断mInputEventConsistencyVerifier不为空则调用onTouchEvent()方法;

■ 然后同样的声明了一个变量handled,并初始化值为false,该值也是最终dispatchTouchEvent()的返回值;

■ 接着判断onFilterTouchEventForSecurity()的返回值,该方法是view的方法:

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

看该方法的注释:Filter the touch event to apply security policies. 即过滤触摸事件的应用安全策略,一般值为true;所以事件将继续往下走;

■ 接下来调用了onInterceptTouchEvent()方法,并用intercepted变量来记录是否拦截的Boolean值;

■ 然后判断if (!canceled && !intercepted) 则继续处理事件,通过for循环遍历该viewGroup的子View,调用dispatchTransformedTouchEvent()来进行分发操作,而该方法是view的一个方法:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            return handled;
        }

        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else { 
                    handled = child.dispatchTouchEvent(event);
                }
                return handled;
            }
        }

        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        return handled;
    }

可以看到,通过child.dispatchTouchEvent进行分发;

总结

要想随心所欲的自定义View,事件分发就要玩的遛,通过对事件的处理和拦截,可以很轻松的处理滑动冲突等问题,本文如有不足之处,还望多多指教。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值