【Android View事件分发机制】原理
来源:互联网 发布:java泛型常用的地方 编辑:程序博客网 时间:2024/05/30 13:42
事件体系中的几个基础类
MotionEvent
点击事件的封装。
getX/Y
相当于当前View左上角的x,y坐标
getRawX/Y
相对于手机屏幕左上角的x,y坐标
GestureDetector 手势识别器
@Override public boolean onTouchEvent(MotionEvent event) { if (gestureDetector == null) { gestureDetector = new GestureDetector(getContext(), this); gestureDetector.setOnDoubleTapListener(this); } gestureDetector.onTouchEvent(event);
VelocityTracker 移动速度计算器
@Override public boolean onTouchEvent(MotionEvent event) { if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } velocityTracker.addMovement(event); velocityTracker.computeCurrentVelocity(100); Log.d(tag, "X方向实时速度:" + velocityTracker.getXVelocity() + " Y方向实时速度:" + velocityTracker.getYVelocity());
- addMovement 收集事件,速度计算的素材
- computeCurrentVelocity 计算速度,入参如果是1000,输出速度的单位就是像素/秒 ,如果是1,单位就是像素/毫秒。
- getXVelocity()/getYVelocity() 取到X/Y方向的速度。
TouchSlop
系统能识别的最小滑动距离 ——滑动的最小粒度,如果小于这个粒度的move,将不认为有过move事件发生。
Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
事件要么被底层的View处理,要么事件将由Activity的onTouchEvent来执行。
getWindow是什么?
phoneWindow是window的实现。
PhoneWindow的superDispatchTouchEvent
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
mDecor是什么?
// This is the top-level view of the window, containing the window decor.private DecorView mDecor;
在PhoneWindow中的顶层view:DecorView.而DecorView本身就是FrameLayout,
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event);}
那么其实最终调用的就是ViewGroup的dispatchTouchEvent方法。
ViewGroup的dispatchTouchEvent
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); // 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 group continues to intercept touches. intercepted = true;}
- 1.当为Down事件时,肯定不存在FLAG_DISALLOW_INTERCEPT(不允许拦截)的标志(因为Down事件会清空所有状态),所以必定会走onInterceptTouchEvent的方法。
- 2.mFirstTouchTarget==null的话,意味着没有child View能够处理该Touch Event ,如果此刻事件不是Down,那么事件将强制由自己拦截处理。所以intercepted强置为true。
for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex); ..... 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; } .... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; }
- newTouchTarget!=null 就break的意思是:前面的事件过程中已经找到了会处理touch event的child view,则不用再去查找了。
- newTouchTarget==null的时候就需要逐一查找了,dispatchTransformedTouchEvent会间接的调用对应child view的dispatchTouchEvent,最终会调用到onTouchEvent, 如果处理该事件返回true,执行addTouchTarget(后面专门讲),并将alreadyDispatchedToNewTouchTarget置为true,它的意义是告诉后面的过程,在dispatchTransformedTouchEvent中已经执行touch event的处理,所以不要再做了。
addTouchTarget mFirstTouchTarget
private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
- child View会被封装为一个TouchTarget.
- 链表形式储存管理
- mFirstTouchTarget存储最近添加进来的TouchTarget
- 什么时候清空呢?在Down事件触发时,执行resetTouchState中,会将该链表整个的清空掉。
回头再看getTouchTarget
private TouchTarget getTouchTarget(View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { if (target.child == child) { return target; } } return null; }
遍历整个TouchTarget链表,查看child view是否跟链表中的某个touchTarget有关联,存在则说明该child view可处理该touch event.
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { .... if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } .... } predecessor = target; target = next; } }
- mFirstTouchTarget是链表之首,如果它为空,则说明TouchTarget链表为空,该viewGroup下没有任何child view会处理该touch event, 调用dispatchTransformedTouchEvent且入参child为null,结果是viewGroup会调用自身的dispatchTouchEvent,最终会将自己onTouchEvent执行。详情如下:
//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; }
- 当mFirstTouchTarget不为空,说明child view能够处理该touch event。alreadyDispatchedToNewTouchTarget为true的时候,只是将handled置为true就啥也没干,原因前面已经讲了。其他情况时,会执行dispatchTransformedTouchEvent,这个函数的作用就是将事件分发给可处理该touch event的child view.
- 利用while (target != null)循环遍历整个TouchTarget链表,将事件分发给整个链表的child view.
时序图
来个Demo
页面结构
试验1:没有任何childView处理TouchEvent
试验2:最里层的 TextView处理TouchEvent
现象1
在Down事件时,ViewGroup下的没有任何childView处理TouchEvent,那么Up事件到来时,事件并没有下发到我们的布局中。
原因
顶层ViewGroup:DecorView拦截了该touch event,所以事件不会再下发到. 具体原因见上面小节 ViewGroup的dispatchTouchEvent的第2点。
得出结论:
如果没有任何childView处理TouchEvent,后续的touch event不再传递下去,父级会自己拦截处理。
现象2
如果ViewGroup下的没有任何childView处理TouchEvent,则父级的onTouchEvent会执行,否则父级的onTouchEvent不会执行。
原因
如果没有找到处理touchEvent的childView, touch event会被dispatch给自身,自身的onTouchEvent就会被调用到。
得出结论
如果ViewGroup下的没有任何childView处理TouchEvent,则父级的onTouchEvent会执行,否则父级的onTouchEvent不会执行。
现象3
ViewGroup 有onInterceptTouchEvent方法,而View是没有的!
原因
功能决定:ViewGroup 是可以作为View容器的,事件往child views下发的权利由它来掌控,所以这个控制节点就由onInterceptTouchEvent方法来做,但是普通的View是没有这个控制权利的,固然也不需要这个方法。
比较试验1和试验2,如果child view有处理touch event,则ViewGroup的onTouchEvent是没有机会执行的,那么如果ViewGroup想让自己来处理这个事件呢?那只有利用onInterceptTouchEvent返回true来将事件拦截下来。
结论
onInterceptTouchEvent是ViewGroup所独有的,是控制事件往下下发的关卡。ViewGroup的onTouchEvent获得执行机会有两种情况:
- 其属下child view都不处理touch event,事件处理返回到ViewGroup。
- 覆写onInterceptTouchEvent并返回true.
Note:
- 上面对触控事件的讨论都是基于从Down开始到Up结束所包含的触控事件集合,因为在Down开始之初会将之前的状态clear掉。
- 【Android View事件分发机制】原理
- android View事件分发机制。
- Android View事件分发机制
- android view事件分发机制
- Android View 事件分发机制
- Android:View事件分发机制
- Android事件分发机制-------View
- Android View事件分发机制
- android事件分发机制view
- Android View 事件分发机制
- Android View事件分发机制
- Android View 事件分发机制
- Android View事件分发机制
- Android View事件分发机制
- android事件分发机制 VIew的事件分发机制
- Android 事件分发机制解析之View的事件分发
- Android View 事件分发机制 源码解析
- Android View、ViewGroup 事件分发机制(一)
- Cash Machine
- 魔咒词典
- 公钥与私钥,HTTPS详解
- OpenCV之imread解析
- 293. Flip Game
- 【Android View事件分发机制】原理
- Linux命令系列(2):cd命令
- Akka(1):Actor
- awk工具
- boot 库编译
- Vijos 1433题:火炬手之梦
- Being a Good Boy in Spring Festival
- 详说vC++中 string之万能转换方法
- 前端必备----CSS知识总结(一)