Android View事件分发机制

来源:互联网 发布:c语言abs 编辑:程序博客网 时间:2024/05/29 10:38

背景

在开发中,我们经常需要自定义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
原创粉丝点击