ViewGroup如何分发事件
dispatchTouchEvent
事件派发显示隧道方式、再是冒泡方式
隧道方式传递,直道某一个元素消耗此事件,由上至下逐层分发视图。
冒泡方式传递,当某个视图消耗事件后其return boolean 是与分发相反的方法向上传递。
具体分发给哪一个视图是通过当前触摸点坐标在当前层哪个视图上判断 onInterceptTouchEvent
ViewGroup的方法,如果当前ViewGroup需要拦截传递给其子视图的事件,需要return true
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
} boolean handled = false;
// 2. 未被其他窗口遮盖
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
// 3. 清理触摸操作的所有痕迹,即派发取消操作
cancelAndClearTouchTargets(ev);
resetTouchState();
} // 检测是否拦截Touch Event
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 是否允许拦截,可以通过requestDisallowInterceptTouchEvent方法设置
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;
} // Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 如果Touch Event没有取消并且没有被拦截,才会考虑是否向其子视图派发
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex); // 遍历当前ViewGroup的所有子视图
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
// 4 与5
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
// 满足以上条件,没必要接收Touch Event
continue;
} // 如果是在子视图上触摸,经过以上过滤条件,只有当前手指正下方的子视图才会获取到此事件
// 子视图是否在TouchTarget中
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;
} resetCancelNextUpFlag(child);
// 6. 执行触摸操作(会传递到最终视图的onTouchEvent方法)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 会改变mFirstTouchTarget的值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
} // 没有发现可以接收事件的子视图
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
} // 子视图未消耗Touch Event 或者 被拦截未向子视图派发Touch Event
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 由当前ViewGroup处理Touch Event
// 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 {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
} // Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
} // 1. 用于测试目,直接忽略
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
/**
* Cancels and clears all touch targets.
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
// ## 如果指向其他视图,这里进行统一清理
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
// 如果参数为空
final long now = SystemClock.uptimeMillis();
// 使用静态方法一个MotionEvent对象,动作为ACTION_CANCEL
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
// 输入源是一个触摸屏定位设备。
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
} for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
// 6 向下传递触摸事件(当前传递取消操作)
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
// 清理所有触摸目标
clearTouchTargets(); // 注销拷贝的对象
if (syntheticEvent) {
event.recycle();
}
}
}
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
private static boolean canViewReceivePointerEvents(View child) {
// 当前视图显示或者正在执行动画,才可以接受触摸事件
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
* @hide
*/
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
// 视图有scrollTo或者scrollBy造成的滚动偏移也需要计算在内
float localX = x + mScrollX - child.mLeft;
float localY = y + mScrollY - child.mTop;
if (! child.hasIdentityMatrix() && mAttachInfo != null) {
final float[] localXY = mAttachInfo.mTmpTransformLocation;
localXY[0] = localX;
localXY[1] = localY;
child.getInverseMatrix().mapPoints(localXY);
localX = localXY[0];
localY = localXY[1];
}
// 触摸点是否在当前子视图内
final boolean isInView = child.pointInView(localX, localY);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(localX, localY);
}
return isInView;
}
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant 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 motions is a special case. We don't 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) {
// 调用View类的方法,其内部会调用View.onTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
// 如果有子视图的话,向下传递取消动作
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
// 针对取消操作特别处理
return handled;
} // Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
} // If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
// 调用View类的方法,其内部会调用View.onTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY); // 如果当前视图通过scrollTo或scrollBy对子视图进行滚动
// 向下传递的是x,y偏移mScrollX, mScrollY后的值
handled = child.dispatchTouchEvent(event); // 一次手势操作,例如滚动视图,MotionEvent参数都是同一个对象
// 如果有疑问的话,可以在onTouchEvent中打印其hashCode看下
// 所以这里要对之前做出针对其子视图的偏移就还原,便于之后使用
event.offsetLocation(-offsetX, -offsetY);
}
// 如果以上消耗了当前触摸事件,直接返回
return handled;
}
// 获取当前参数的拷贝,对其做得修改不会影响当前参数对象
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
} // 与以上操作雷同,不具体解释
// newPointerIdBits != oldPointerIdBits ?1
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
} handled = child.dispatchTouchEvent(transformedEvent);
} // Done.
transformedEvent.recycle();
return handled;
}
ViewGroup如何分发事件的更多相关文章
- View,ViewGroup的Touch事件的分发机制
原帖地址:http://blog.csdn.net/xiaanming/article/details/21696315 ViewGroup的事件分发机制 我们用手指去触摸Android手机屏幕,就会 ...
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
转自:xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315) 今天这篇文章主要分析的是Android的事件分发机制, ...
- ViewGroup 和 View 事件传递及处理小谈
前言 在自定义组件的时候少不了会去处理一些事件相关的东西,关于事件这块网上有很多文章,有说的对的也有说的不对的,我在理解的时候也有过一段时间的迷惑,现在把自己理解的东西写下来,给有相同疑问的朋友提供些 ...
- (备忘)自定义viewgroup与点击分发事件
public class ScoreButton extends ViewGroup 在类中重写onTouchEvent方法 @Override public boolean onTouchEvent ...
- Android ViewGroup拦截触摸事件具体解释
前言 在自己定义ViewGroup中.有时候须要实现触摸事件拦截.比方ListView下拉刷新就是典型的触摸事件拦截的样例. 触摸事件拦截就是在触摸事件被parent view拦截,而不会分发给其ch ...
- as3 分发事件无法接收
最简单的.直接用舞台接收. 如: stage.addEventListener("ok",okH);
- 本以为精通Android事件分发机制,没想到被面试官问懵了
文章中出现的源码均基于8.0 前言 事件分发机制不仅仅是核心知识点更是难点,并且还是View的一大难题滑动冲突解决方法的理论基础,因此掌握好View的事件分发机制是十分重要的. 一.基本认识 1. 事 ...
- Android艺术开发探索第三章————View的事件体系(下)
Android艺术开发探索第三章----View的事件体系(下) 在这里就能学习到很多,主要还是对View的事件分发做一个体系的了解 一.View的事件分发 上篇大致的说了一下View的基础知识和滑动 ...
- Android事件分发机制浅谈(二)--源码分析(ViewGroup篇)
上节我们大致了解了事件分发机制的内容,大概流程,这一节来分析下事件分发的源代码. 我们先来分析ViewGroup中dispatchTouchEvent()中的源码 public boolean dis ...
随机推荐
- 读CSS DIV网页样式与布局心得体会
一.首先根据网页设计图拆分网页的整体结构 二.在html页面用DIV划分出结构块 三.再根据设计图在各个大<DIV>块中加入对应的小<DIV>块或者段落<P>,表单 ...
- ubuntu上传项目到github
https://blog.csdn.net/ajianyingxiaoqinghan/article/details/70544159
- main()中的参数argc, argv
转自:http://blog.csdn.net/eastmount/article/details/20413773 一.main()函数参数 通常我们在写主函数时都是void main()或int ...
- shell的while循环
while循环用于不断执行一系列命令,也用于从输入文件中读取数据:命令通常为测试条件.其格式为: while command do Statement(s) to be executed if ...
- 利用ps,grep,kill联合杀掉相关进程
#!/bin/sh . "./set-server-env.sh" 这里是输出ps -ef |grep java 结果的第二列的内容然后通过xargs传递给kill -9,其实第二 ...
- HashTable的构造函数有哪些
HashTable:在并发的环境下,使用synchronized将整张表锁住: HashTable构造函数有: public Hashtable(int initialCapacity, float ...
- VirtualBox 下主机与虚拟机以及虚拟机之间互通信配置
引用链接:1)http://www.it165.net/os/html/201401/7063.html 2)http://www.cnblogs.com/sineatos/p/4489620.htm ...
- Log4j2异步情况下怎么防止丢日志的源码分析以及队列等待和拒绝策略分析
org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor以下所有源码均在此类中首先我们看下log4j2异步队列的初始化 从这里面我们 ...
- 金鹰dreamweaver视频教程下载地址
原文发布时间为:2008-07-30 -- 来源于本人的百度文章 [由搬家工具导入] http://download2.gbaopan.com/c188aecb5e524ab2b2a484c061e0 ...
- 频繁项挖掘算法Apriori和FGrowth
一:背景介绍 最近在公司用spark的平台做了一个购物车的推荐,用到的算法主要是FGrowth算法,它是Apriori算法的升级版,算法的主要目的是找出频繁进行一起购买的商品.本文主要介绍两个算法的背 ...