昨天晚上从源码角度复习了一下android的事件分发机制,今天将笔记整理下放在网上。其实说复习,也是按着《android开发艺术探索》这本书做者的思路作的笔记。android
android的事件分发过程大体为Activity-->ViewGroup-->View,当咱们点击屏幕的时候产生事件,系统会调用的ActivitydispatchTouchEvent()
开始事件的传播过程,下面咱们看一下Activity的dispatchTouchEvent()源码:bash
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//当用户操做了按键或者触摸了屏幕,就会回调用该函数
onUserInteraction();
}
//调用window的superDispatchTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
Activity的dispatchTouchEvent()在它的ACTION_DOWN事件中调用onUserInteraction()
这个函数能够用来监听用户的一些按键和触摸操做。而后就会调用window的superDispatchTouchEvent()方法
将事件传播到window中,若是window的superDispatchTouchEvent()返回true则表明事件被消耗了,反之表明事件没有被处理,这个时候Activity就会调用本身的onTouchEvent()来处理。ide
事件传播到Window中该类是一个抽象类,它的superDispatchTouchEvent()方法是一个抽象方法。咱们须要查看它的实现类PhoneWindow
中查看事件的执行过程,下面为PhoneWindow的superDispatchTouchEvent()源码:函数
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//将事件传递给DecorView
return mDecor.superDispatchTouchEvent(event);
}
复制代码
PhoneWindow的superDispatchTouchEvent()中就一行代码,就是将事件传递给顶层View
的superDispatchTouchEvent()。DecorView有将该方法传递给他父类dispatchTouchEvent()
。若是读者对DecorView有所了解,应该知道DecorView是FrameLayout的一个子类,也就是说事件传递到了ViewGroup的dispatchTouchEvent()方法中了。post
ViewGroup的dispatchTouchEvent()方法代码很长,咱们先看下面一本分代码:动画
if (actionMasked == MotionEvent.ACTION_DOWN) {
//将以前的状态清除
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//默认状况下他们两个比较的结果为0
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
复制代码
从上面的代码咱们能够知道,若是ViewGroup分发的是ACTION_DOWN事件,那么FLAG_DISALLOW_INTERCEPT这个标记位将会被重置,即便子View经过requestDisallowInterceptTouchEvent()
设置了该标记为。也就是说分发下来的是ACTION_DOWN事件的话ViewGroup将会调用本身的onInterceptTouchEvent()
寻问是否拦截。若是ViewGroup不拦截ACTION_DOWN事件,那么事件将会向它的子View传递:ui
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--){
...
//经过dispatchTransformedTouchEvent()将事件传递给子View
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 < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//对mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
复制代码
ViewGroup首先会遍历子全部View,而后判断子元素是否能接受这个点击事件。主要是经过两点,子元素是否在播放动画和点击事件的着落点是否在子元素的区域内。若是知足上面的两点那么事件将会传递给他处理。dispatchTransformedTouchEvent()
实际上就是子元素的dispatchTouchEvent()方法。this
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
复制代码
若是子元素的dispatchTounchEvnent()返回的为true,那么mFirstTouchTarget
将会赋值而且跳出循环,若是为false将会进入下次循环继续遍历子View。spa
//对mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
复制代码
下面为addTouchTarget()函数的代码:.net
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//mFirstTouchTarget为链表结构
mFirstTouchTarget = target;
return target;
}
复制代码
mFirstTouchTarget是否赋值,将会影响ViewGroup的拦截策略。若是mFirstTouchTarget为null,那么ViewGroup将会拦截下来同一序列的全部事件。那mFirstTouchTarget在什么状况下才为null呢?通常在两种状况下,要么是ViewGroup遍历了全部的子元素事件没有被处理;要么是子元素处理了ACTION_DOWN可是dispatchTouchEvent返回为false。这两种状况下ViewGroup会处理该事件,而且后续的事件也将交给他处理再也不向子元素传递。
if (mFirstTouchTarget == null) {
// 出入的第三个参数为null,表明事件交给ViewGroup本身处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
复制代码
View的事件处理比ViewGroup要简单,他首先会判断是否设置是否设置了onTouchListener()函数。
下面为View的dispatchTouchEvent()部分代码:
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//首先判断是否设置了onTouchListener()
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//调用onTouchEvent(event)
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
复制代码
从上面代码可知,若是咱们设置了onTouchListener()那么它会调用onTouch()方法,而且onTouch()返回值将会影响对View的onTouchEvent(event)函数的调用,若是返回true将不会调用。因而可知onTouch()的优先级要高于onTouchEvent()。
public boolean onTouchEvent(MotionEvent event) {
...
//若是View设有代理,将会执行TouchDelegate.onTouchEvent(event)
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//只要View的CLICKABLE和LONG_CLICKABLE有一个返回true,他就会被消耗这个事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//点击事件
if (!post(mPerformClick)) {
performClick();
}
}
...
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
//长安事件
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
...
break;
...
}
return true;
}
return false;
}
复制代码
从上面的代码能够看出,View的点击事件是在ACTION_UP事件中调用了performClick()
方法处理,长按事件是在ACTION_DOWN事件中调用了checkForLongClick()
方法处理。
文章到这里也把android的分发机制从源码的角度分析的差很少了,读者应该对android的事件分发机制应该会有有一个比较全面的了解了。下篇文章我将记录一下关于事件冲突的笔记,若是感兴趣能够继续关注。