注册

android 事件分发机制

Android 事件分发机制解析

1. view的事件分发机制

view的事件分发是从 dispatchTouchEvent() 开始的,直接上代码;

public boolean dispatchTouchEvent(MotionEvent event) {  
boolean result = false;
// 1. view 是否可以点击 && setOnTouchListener 有值 并且 setOnTouchListener 返回值是true 事件分发 结束

if ( (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener != null &&
mOnTouchListener.onTouch(this, event)) {
boolean result = true;

}
// 2.如果上述条件不都成立 执行 OnTouchEvent();
if (!result && onTouchEvent(event)) {
result = true;
}


return result;
}

/**
* 分析1:onTouchEvent()
*/
public boolean onTouchEvent(MotionEvent event) {



// 若该控件可点击,则进入switch判断中
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

// 根据当前事件类型进行判断处理
switch (event.getAction()) {

// a. 事件类型=抬起View(主要分析)
case MotionEvent.ACTION_UP:
performClick();
// ->>分析2
break;

// b. 事件类型=按下View
case MotionEvent.ACTION_DOWN:
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;

// c. 事件类型=结束事件
case MotionEvent.ACTION_CANCEL:
refreshDrawableState();
removeTapCallback();
break;

// d. 事件类型=滑动View
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();

int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}

// 若该控件可点击,就一定返回true
return true;
}
// 若该控件不可点击,就一定返回false
return false;
}

**
* 分析2:performClick()
*/
public boolean performClick() {

if (mOnClickListener != null) {
// 只要通过setOnClickListener()为控件View注册1个点击事件
// 那么就会给mOnClickListener变量赋值(即不为空)
// 则会往下回调onClick() & performClick()返回true
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}

总结:如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

image.png

2.ViewGroup 事件分发机制

viewGroup事件分发可以分为以下阶段:1.点击事件是down,将mFirstTarget、其他的标记、状态值清空,2.检查当前事件是否被拦截,3.如果被拦截,当前的事件不会分发给子view(firstTarget为空),会交由viewGroup父类的dispatchTouchEvent处理;4.如果不拦截,会找到一个满足条件的子view,分发此次的down事件;5.如果找不到满足条件的子view,firstTouch=null,就会调用自身的dispatchTouchEvent;6.如何当前点击事件是move、up时;6.如果找到了符合条件的子view,把down事件分发给子view,并对firstTouchTarget赋值,down事件分发结束;7.接来下就是move、up事件的分发,如果down事件分发给子view了,会再次判断是否拦截;8.如果不拦截,就会把move、up分发给mFirstTouchTarget对应的子view;9.如果拦截,会分发一个cancel事件给firstTouchTarget对应的子view。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

// 1.将firstTouchTarget置空,其他状态清空
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}

final boolean intercepted;


/***
* 2.检查是否拦截事件,如果点击事件是down、或者事件已经分发给子view,通过viewGroup的
* onInterceptTouchEvent 判断
*
*/

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 {
intercepted = true;
}


TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 3. 如果事件没有被拦截,会需找一个满足条件的子view分发事件
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {


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();
/***
*4. 如果有子view可以分发当前的事件,对newTouchTarget,firstTouchTarget赋值,记
* 消费本次事件的view
*/
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}

if (mFirstTouchTarget == null) {
// 5. 事件交由viewGroup父类的dispatchTouchEvent 处理
// 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;
// 6.找到符合条件的子view,该事件分发结束
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) { // 如果拦截了事件,清空 firstTouchTarget
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}

}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;


final int oldAction = event.getAction();
// 如果是子view消费了viewGroup分发的事件,后续事件被viewGroup拦截,viewGroup会发送一
cancel事件给firstTouchTarget对应的子view,该事件结束。下一个事件就不会再分发给子view了。

if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}


final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;


if (newPointerIdBits == 0) {
return false;
}

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);
}
transformedEvent.recycle();
return handled;
}

3.滑动冲突

3.1 滑动冲突场景

方向一致:父容器和子view的滑动方向一致,如:scrollView 嵌套一个recyclewView 方向不一致:父容器和子view的滑动方向不一致,如scrollView 嵌套一个 viewPage。

3.2 外部拦截法

子view需要处理事件时,在父容器里面通过onInterceptTouchEvent返回值为false,让事件交由子view处理;当父容器需要处理事件时,让onInterceptTouchEvent返回值未true,让父容器拦截子view的事件,自己处理事件。伪代码如下:

public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted=false;
int x= (int) event.getX();
int y= (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted=false;//必须不能拦截,否则后续的ACTION_MOME和ACTION_UP事件都会拦截。
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件){
intercepted=true;
}else {
intercepted=false;
}
break;
case MotionEvent.ACTION_UP:
intercepted=false;
break;

default:
break;
}
mLastXIntercept=x;
mLastXIntercept=y;
return intercepted;
}

3.3 内部拦截法

if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}

内部拦截法通过子view(requestDisallowInterceptTouchEvent(disallowIntercept=false),disallowIntercept=false)改变viewGroup的disallowIntercept值,来干预viewGroup是否拦截子view。从上面代码,我们可以知道:disallowIntercept只能控制让viewGroup不拦截子view,拦截子view是通过viewGroup的 onInterceptTouchEvent方法值控制的。所以内部拦截法,就是结合viewGroup的 onInterceptTouchEvent方法和view通过viewgroup.requestDisallowInterceptTouchEvent改变 disallowIntercept值共同来完成。

// 重写 viewGroup  onInterceptTouchEvent方法,down返回值不能为false
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}

//  重写子view的 dispatchTouchEvent事件 
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
//如果是左右滑动
if (父容器) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
}
mLastXIntercept = x;
mLastYIntercept = y;
return super.dispatchTouchEvent(ev);
}


0 个评论

要回复文章请先登录注册