Android View的事件分发机制_第1页
Android View的事件分发机制_第2页
Android View的事件分发机制_第3页
Android View的事件分发机制_第4页
Android View的事件分发机制_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Android View的事件分发机制一个应用的布局是丰富的,有TextView,ImageView,Button等,这些子View的外层还有ViewGroup,如RelativeLayout,LinearLayout。作为一个开发者,我们会思考,当点击一个按钮,Android系统是怎样确定我点的就是按钮而不是TextView的?然后还正确的响应了按钮的点击事件。内部经过了一系列什么过程呢?先铺垫一些知识能更加清晰的理解事件分发机制: 1. 通过setContentView设置的View就是DecorView的子view,即DecorView是父容器。 2. 点击屏幕时,在手指按下和抬起间,会

2、产生很多事件,downmovemoveup,中间会有很多的move事件,这一系列的事件为一个事件序列 3. dispatchTouchEvent方法用于分发事件 4. onInterceptTouchEvent方法用于拦截事件 5. onTouchEvent方法用于处理事件当一个点击事件(MotionEvent)产生后,事件最先传递给当前的界面(Activity),这点是很好理解的。 Activity再将事件传递给窗口(Window),然后Window将事件传递给顶级View(DecorView)。此时,事件已经到达了View了。之后顶级View就会按照事件分发机制去分发事件。具体是这样的:对

3、于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用。如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。 如果一个View的onTo

4、uchEvent方法返回false,那么它的父容器的onTouchEvent方法会被调用,如果它的父容器的onTouchEvent方法还是返回false,那就继续往上抛,当所有的元素都不处理这个事件,那么这个事件会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。好了,现在已经铺垫了基础,那么接下来就从源码的角度来分析事件分发机制。当然是从Activity的dispatchTouchEvent方法开始分析。源码如下:public boolean dispatchTouchEvent(MotionEvent ev) if (ev.getAction() =

5、 MotionEvent.ACTION_DOWN) onUserInteraction(); if (getWindow().superDispatchTouchEvent(ev) return true; return onTouchEvent(ev);如果当前事件是down的话,就调用onUserInteraction方法,onUserInteraction是一个空方法,我们可以暂时不搭理。然后调用getWindow方法获取到当前Activity关联的Window,Window再调用superDispatchTouchEvent方法将事件传入进行分发。 如果superDispatchTou

6、chEvent方法返回true的话, view已经处理了事件。整个事件循环结束。如果返回false,没有view处理这个事件。事件往上抛,那就Activity自己处理了,即Activity的onTouchEvent方法会被调用。因为想要知道事件的整个分发过程,现在关注的是Window的superDispatchTouchEvent方法,那么就跟进去看看:public abstract boolean superDispatchTouchEvent(MotionEvent event);Window是一个抽象类,superDispatchTouchEvent是一个抽象的方法,那么我们必须要找到w

7、indow的实现类才行,可是茫茫人海怎么找呢?看到window类的说明就明白了* The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */public abstract class Window意思是Window存在唯一的实现是android.view.PhoneWindow那么PhoneWindow里的superDispatchTouchEvent方法就是我们要找的

8、信息,如下:Overridepublic boolean superDispatchTouchEvent(MotionEvent event) return mDecor.superDispatchTouchEvent(event);直接将事件传递给了DecorView。这时事件已经是到达View了哦。那么跟进DecorView的superDispatchTouchEvent方法看看,如下:public boolean superDispatchTouchEvent(MotionEvent event) return super.dispatchTouchEvent(event);内部调用了父

9、类的dispatchTouchEvent方法,那么DecorView的父类是什么呢?DecorView肯定是View的,那么刚才开篇提到,我们通过setContentView设置的View,是DecorView的子View。那么更加准确的说DecorView是一个ViewGroup。private final class DecorView extends FrameLayout implements RootViewSurfaceTaker可以看到DecorView是继承自FrameLayout,FrameLayout是ViewGroup,也就是说DecorView是一个ViewGroup。

10、那么现在只需要关注ViewGroup的dispatchTouchEvent方法。继续前进ViewGroup的事件分发ViewGroup的dispatchTouchEvent方法如下:Overridepublic boolean dispatchTouchEvent(MotionEvent ev) /代码省略 / Check for interception. final boolean intercepted; if (actionMasked = MotionEvent.ACTION_DOWN | mFirstTouchTarget != null) final boolean disall

11、owIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) intercepted = onInterceptTouchEvent(ev); ev.setAction(action); / restore action in case it was changed else intercepted = false; else / There are no touch targets and this action is not an initial down / so this view

12、 group continues to intercept touches. intercepted = true; /代码省略 if (!canceled & !intercepted) /代码省略 final int childrenCount = mChildrenCount; if (newTouchTarget = null & childrenCount != 0) final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); / Find a child that can receive t

13、he event. / Scan children from front to back. final ArrayList preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList = null & isChildrenDrawingOrderEnabled(); final View children = mChildren; for (int i = childrenCount - 1; i = 0; i-) final int childIndex = customOrder

14、? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList = null) ? childrenchildIndex : preorderedList.get(childIndex); / If there is a view that has accessibility focus we want it / to get the event first and if not handled we will perform a / normal dispatch. We may do a do

15、uble iteration but this is / safer given the timeframe. if (childWithAccessibilityFocus != null) if (childWithAccessibilityFocus != child) continue; childWithAccessibilityFocus = null; i = childrenCount - 1; if (!canViewReceivePointerEvents(child) | !isTransformedTouchPointInView(x, y, child, null)

16、ev.setTargetAccessibilityFocus(false); continue; newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) / Child is already receiving touch within its bounds. / Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; resetCan

17、celNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) / Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) / childIndex points into presorted list, find original index for (int j = 0; j = 0; i-)这时View

18、Group开始分发传递事件,遍历子元素了。首先肯定需要过滤掉一些无关点击事件的子元素的,判断子元素是否能够接收点击事件,点击事件的坐标是否落在子元素区域内。if (!canViewReceivePointerEvents(child) | !isTransformedTouchPointInView(x, y, child, null) ev.setTargetAccessibilityFocus(false); continue;如果不能够接收点击事件或者点击事件的坐标没有落在子元素区域,就会跳出当前循环,继续遍历下一个子元素。这下就知道了Android系统为什么能够知道点击的是Button

19、而不是TextView,其实内部就只是做了一个判断嘛。那么继续分析,子元素符合以上两个条件后,就将事件传递给这个子元素。会来到了这个判断。if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)执行dispatchTransformedTouchEvent方法,将子元素传进去。这个方法很重要,那么跟进看看/* * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevan

20、t pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) final boolean handled; / Canceling motion

21、s is a special case. We dont need to perform any transformations / or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel | oldAction = MotionEvent.ACTION_CANCEL) event.setAction(MotionEvent.ACTION_CANCEL); if (child = null) handled = su

22、per.dispatchTouchEvent(event); else handled = child.dispatchTouchEvent(event); event.setAction(oldAction); return handled; /代码省略我们看到child!=null的情况,如果子元素不为空,调用子元素的dispatchTouchEvent方法继续分发事件,同时返回处理结果布尔值,这时就将事件传递到了子View处理。完成了一轮的事件分发。这个方法先到这里就好。再看回ViewGroup的dispatchTouchEvent方法,如果dispatchTransformedTouc

23、hEvent方法返回true的话,这时事件已经传递给子元素处理,ViewGroup已经不管这个事件了。 那么就会进入if语句,最后会来到addTouchTarget方法,这个方法之前是提到过的,用于mFirstTouchTarget标记位的赋值。那跟进这个方法看看/* * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */private TouchTarget addTouchTarget(View

24、child, int pointerIdBits) TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target;其实就是让mFirstTouchTarget指向子元素。执行完这个addTouchTarget方法后,最终会到break语句,那么就会跳出整个for循环体。ViewGroup结束分发过程!又回到dispatchTransformedTouchEvent方法,如果dispa

25、tchTransformedTouchEvent方法返回false,那么if语句的一大段代码都不执行了,而是回到for循环继续遍历子元素进行分发。如此重复完成事件的传递过程。现在分析ViewGroup拦截事件的情况,如果ViewGroup拦截事件的话,那么就会进入以下这个判断if (mFirstTouchTarget = null) / No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_PO

26、INTER_IDS);注意到dispatchTransformedTouchEvent方法的第三个参数child传入的是null,那么就是在dispatchTransformedTouchEvent方法中走以下的语句if (child = null) handled = super.dispatchTouchEvent(event);而ViewGroup是继承自View的,那么就是ViewGroup自己处理事件了。这点我们以下分析了View的事件分发过程就能搞明白了。以上就是ViewGroup的事件分发那么现在分析已经将事件传递给了子View的情况,View继续调用dispatchTouchE

27、vent方法,那我们看看View的dispatchTouchEvent方法。View的事件分发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. */

28、public boolean dispatchTouchEvent(MotionEvent event) /代码省略 if (onFilterTouchEventForSecurity(event) /noinspection SimplifiableIfStatement ListenerInfo li = mLtenerInfo; if (li != null & li.mOnTouchListener != null & (mViewFlags & ENABLED_MASK) = ENABLED & li.mOnTouchListener.onTouch(this, event) res

29、ult = 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 didnt want

30、 the rest / of the gesture. if (actionMasked = MotionEvent.ACTION_UP | actionMasked = MotionEvent.ACTION_CANCEL | (actionMasked = MotionEvent.ACTION_DOWN & !result) stopNestedScroll(); return result;相比于ViewGroup的dispatchTouchEvent方法,View的dispatchTouchEvent方法代码量少了。也相对简单些了。 首先会来到如下判断:if (li != null &

31、li.mOnTouchListener != null & (mViewFlags & ENABLED_MASK) = ENABLED & li.mOnTouchListener.onTouch(this, event)li变量在哪里被赋值的呢?通常是在setOnClickListener方法或setOnTouchListener方法的时候。public void setOnClickListener(Nullable OnClickListener l) if (!isClickable() setClickable(true); getListenerInfo().mOnClickList

32、ener = l;public void setOnTouchListener(OnTouchListener l) getListenerInfo().mOnTouchListener = l;而这个getListenerInfo()如下:ListenerInfo getListenerInfo() if (mListenerInfo != null) return mListenerInfo; mListenerInfo = new ListenerInfo(); return mListenerInfo;ListenerInfo是一个内部类,里面存放的是各种监听事件的引用。之后会判断如下

33、条件:li.mOnTouchListener != null同理只要setOnTouchListener方法设置了,这个引用就不空。这些都是好理解的。那关键到了,li.mOnTouchListener.onTouch(this, event)到了最后一个条件。这个onTouch方法是我们去实现的,它也返回一个布尔值,如果返回true的话,那么就会进入这个if判断最终返回true,跳出整个方法,那么我们可以看到接下来的onTouchEvent方法是不会得到执行的。 也就是onTouch的执行在onTouchEvent之前。那么如果我们也调用了setOnClickListener方法监听点击事件的

34、话,onClick方法是在哪里调用的呢?我们有理由相信是在onTouchEvent方法里调用的。那么就跟进看看。public boolean onTouchEvent(MotionEvent event) /代码省略 if (viewFlags & CLICKABLE) = CLICKABLE | (viewFlags & LONG_CLICKABLE) = LONG_CLICKABLE) | (viewFlags & CONTEXT_CLICKABLE) = CONTEXT_CLICKABLE) switch (action) case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; /代码省略 if (!focusTaken) / Use a Runnable and post this rather than call

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论