带你从源码角度分析ViewGroup中事件分发流程
来源:互联网 发布:服装生产软件 编辑:程序博客网 时间:2024/05/23 13:11
序言
这篇博文不是对事件分发机制全面的介绍,只是从源码的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分发逻辑,了解各个事件在ViewGroup的分发逻辑对理解、解决滑动冲突问题很有帮助。
ViewGroup中事件分发流程
这里我是用的是2.3.3版本的源码,原因在于这个版本的源码易读,当你理顺了整个分发流程,再去看其他更加高级版本的源码,你会发现思想是一样。如果一个事件传递给ViewGroup,那么dispatchTouchEvent方法会被调用,以下是对dispatchTouchEvent的分析。
public boolean dispatchTouchEvent(MotionEvent ev) { if (!onFilterTouchEventForSecurity(ev)) { return false; } final int action = ev.getAction();//获取事件的坐标 final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; //disallowIntercept 默认是false,//可以通过requestDisallowItercepctTouchEvent来设置参数//被设置成true后,ViewGroup无法拦截除ACTION_DOWN以外的事件(只能拦截Down) boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //这里是ACTION_DOWN的处理逻辑 if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { // this is weird, we got a pen down, but we thought it was // already down! //We should probably send an ACTION_UP to the current target mMotionTarget = null; } // If we're disallowing intercept or if we're allowing and we didn't // intercept//默认情况下disallowIntercept为false,表示允许拦截, //默认情况ViewGroup的onInterceptTouchEvent返回false if (disallowIntercept || !onInterceptTouchEvent(ev)) { // reset this event's action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) {//遍历子View final View child = children[i];//判断子元素是否可以接收到事件,两条件决定//条件一:子View是VISIBLE或者在播动画//条件二:点击坐标落在子View区域内(体现在内嵌的if) if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); //判断是否点击坐标落在子控件区域内 if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //将事件派发给子View,返回true表示子View处理该事件, if (child.dispatchTouchEvent(ev)) { // Event handled, we have a target now. mMotionTarget = child;//将处理事件的目标View保存在变量 return true;//返回true,表示消耗事件 } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { //重置mGroupFlags,//使得在下一个事件ACTION_DOWN来临时disallowIntercept为false mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; if (target == null) {//没有找到可以处理事件的子View // We don't have a target, this means we're handling the // event as a regular view. ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } //子控件不处理,所以此处判断一下自己是否处理 //此时ViewGroup调用的是父类View的dispatchTouchEvent return super.dispatchTouchEvent(ev); } // if have a target, see if we're allowed to and want to intercept its // events//允许并且想要拦截事件 if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL);//设置ACTION_CANCEL ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) {//告知目标子控件事件被拦截 // target didn't handle ACTION_CANCEL. not much we can do // but they should have. } // clear the target mMotionTarget = null;//重置为null,使得下个事件来临时target=null // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true;//返回true,表示事件被消耗 } if (isUpOrCancel) { mMotionTarget = null; } // finally offset the event to the target's coordinate system and // dispatch the event. final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } //将事件分发给目标子控件 return target.dispatchTouchEvent(ev); }
我们知道一些操作会产生事件,比方说在屏幕上滑动一下,这样会产生一系列事件,但这些事件是属于同一序列,它以ACTION_DOWN事件开始,中间有若干个ACTION_MOVE事件,以ACTION_UP事件结束。
ACTION_DOWN事件分发过程
ACTION_DOWN事件被分发到ViewGroup的时候是如何进行逻辑判断的呢,在第31行代码可以看到,首先会通过条件:disallowIntercept||!onInterceptTouchEvent判断是否拦截,disallowIntercept 默认是false,可以通过requestDisallowItercepctTouchEvent来设置参数,若这个条件不成立表示拦截则此时mMotionTarget = null,则来到第80行,那么target = null,表示没有找到可以处理事件的子控件,接下来执行第91行代码,此时调用super.dispatchTouchEvent即View的dispatchTouchEvent方法表示ViewGroup尝试自己处理事件;若条件成立表示不拦截,见第40行代码首先是遍历该ViewGroup的子控件,结合第45、49行代码可以知道在每遍历一个子控件的时候,首先判断子控件是不是visiable或者正在播动画并且点击事件的坐标落在子控件的区域内,假如两个条件同时满足则表明子控件可以接收事件,这时候会来到第55行代码,通过调用child.dispatchTouchEvent将事件分发给子控件,如果child.dispatchTouchEvent返回true,则表示事件被该子控件消耗了,此时执行第57行,该子控件被视作消耗事件的目标View并将其保存mMotionTarget变量中,返回true结束循环遍历;如果child.dispatchTouchEvent返回false,则表示该子控件没有消耗事件,如果该子控件不是ViewGroup遍历的最后一个子控件,则在继续循环遍历下一个子控件。如果此时该子控件是ViewGroup遍历的最后一个子控件,则表明所有的ViewGroup的子控件均不能处理事件,此时循环遍历结束,则mMotiononView = null,程序来到第80行,那么target = null,接下来调用super.dispatchTouchEvent即View的dispatchTouchEvent方法表明ViewGroup尝试自己处理事件.ACTION_DOWN事件用流程图表示如下:
ACTION_MOVE事件分发过程
ACTION_MOVE事件被分发到ViewGroup的时候,从第81行可以看到首先会判断target是否等于null,若等于null,表示子控件均不能消耗事件,则调用super.dispatchTouchEvent即View的dispatchTouchEvent来处理事件。若不等于空,此时会来到第97行,此时通过条件(!disallowIntercept&&onInterceptTouchEvent(ev))判断是否拦截,若条件不成立表示不拦截则执行第131行代码,调用target.dispatchTouchEvent将事件分发给目标子控件处理,如果拦截则首先生成ACTION_CANCEL事件(见第101行)并分发给目标子控件target(见第103行),告知事件已被拦截,之后执行第108行将mMotionTarget重置为null,目的是让接下来的ACTION_UP事件直接能给ViewGroup自己处理,最后在第112行返回true表示事件被消耗。
ACTION_MOVE事件用流程图表示如下:
ACTION_UP事件分发过程
ACTION_UP分发到ViewGroup的时候,首先会通过执行mGrouFlags & = ~FALG_DISALLOW_INTERCEPT使得下一个ACTION_DOWN事件来临时disallowIntecept重置为默认的false,之后的处理逻辑和ACTION_MOVE基本一致,这里不再重复
ACTION_UP事件用流程图表示如下:
1 0
- 带你从源码角度分析ViewGroup中事件分发流程
- 探索Android事件分发机制,带你通过源码分析事件分发流程——ViewGroup篇
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下,ViewGroup篇)
- android 从源码分析ViewGroup事件分发
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- ubuntu在当前目录代开终端
- 欢迎使用CSDN-markdown编辑器
- STM32调试大法 之 串口通讯
- centos7之lamp环境搭建
- MySQL不区分大小写(Linux与WINDOWS)
- 带你从源码角度分析ViewGroup中事件分发流程
- Java多线程基础:进程和线程之由来
- 创建Matlab engine的python binding
- Maven(3)——将本地文件推送到nexus中心库
- Bootstrap入门基础(二)
- POJ-1416 Shredding Company(DFS)
- STM32根据库建立自己的工程
- 设计模式-行为型- 状态模式(State)
- mysql插入数据后返回自增ID的方法