引言:相信不少Android开发都遇到过这样一个问题:“给一个控件写了setOnClickListener,结果View.OnClickListener怎么都不相应,查了半天,发觉原来这个控件有添加setOnTouchListener,一旦View.OnTouchListener中的onTouch返回true,那么View.OnClickListener是绝对不会响应的”。bug好解决,但是背后的原理又是什么呢?
Android事件分发机制
三个对象
Android的事件分发,涉及到三个主要对象:Activity
、ViewGroup
、View,
事件传递的顺序是:Activity
-> ViewGroup
-> View,即
1个点击事件发生后,事件先传到Activity
、再传到ViewGroup
、最终再传到 View。
二个方法
- dispatchTouchEvent:事件分发
- onTouchEvent:事件处理
从Activity开始
/**
* 源码分析:Activity.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 一般事件列开始都是DOWN事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 屏保功能
onUserInteraction();
}
/** 若getWindow().superDispatchTouchEvent(ev)的返回true,则
* Activity.dispatchTouchEvent()就返回true,
*/
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 否则:继续往下调用Activity.onTouchEvent,即ViewGroup不处理该事件,交由Activity自己处理。
return onTouchEvent(ev);
}
注意这里的核心逻辑:getWindow().superDispatchTouchEvent(ev),
- getWindow():获取Window类的对象
- Window类是抽象类,其唯一实现类 = PhoneWindow类;即此处的Window类对象 = PhoneWindow类对象
- Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 顶层View(DecorView)的实例对象
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 调用父类的方法 = ViewGroup的dispatchTouchEvent()
// 即 将事件传递到ViewGroup去处理
}
看到这里,大家应该懂了,Activity的super.dispatchTouchEvent(event)其实就是把事件交给ViewGroup来处理,如果返回true,即ViewGroup处理了该事件,那么Actity也会返回true。而如果super.dispatchTouchEvent(event)返回false,那表示ViewGroup不处理,那么事件就会交由Actity自己的onTouchEvent来处理。
再看ViewGroup
那么,ViewGroup又是怎么处理事件的呢?
/**
* 源码分析:ViewGroup.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev)
{
... // 仅贴出关键代码
// 重点分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
if (disallowIntercept || !onInterceptTouchEvent(ev))
{
// 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用 requestDisallowInterceptTouchEvent()修改
// 判断值2: !onInterceptTouchEvent(ev) = 对onInterceptTouchEvent()返回值取反
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
// 通过for循环,遍历了当前ViewGroup下的所有子View
for (int i = count - 1; i >= 0; i--)
{
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)
{
child.getHitRect(frame);
// 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
// 若是,则进入条件判断内部
if (frame.contains(scrolledXInt, scrolledYInt))
{
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 条件判断的内部调用了该View的dispatchTouchEvent()
// 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
if(child.dispatchTouchEvent(ev))
{
mMotionTarget = child;
return true;
}
// 调用子View的dispatchTouchEvent后是有返回值的
// 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
// 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即把ViewGroup的点击事件拦截掉
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel)
{
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
// 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
// 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
// 此处需与上面区别:子View的dispatchTouchEvent()
}
...
}
简单来说,ViewGroup做了几件事件,通过onInterceptTouchEvent方法判断是否拦截该事件(ViewGroup独有)。如果不拦截,遍历自己的child(即ViewGroup中的View),看事件点击是在那个child身上,调用child的dispatchTouchEvent把事件交给view来处理。到这里,处理逻辑类似于之前的Activity,判断child的dispatchTouchEvent返回是否为true,如果返回true,说明child处理了该事件,整个方法直接返回。如果返回false,或者之前onInterceptTouchEvent返回true,则事件交由ViewGroup自己的super.dispatchTouchEvent(ev)来处理。由于ViewGroup继承自View,因此其实这里调用了View的dispatchTouchEvent,最后会调用View的OnTouchEvent处理这个事件。如果这里还返回false,那整个方法返回false,事件重新回到Activity,还记得刚才Activity的处理逻辑嘛?
最后分析View
/**
* 源码分析:View.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent event)
{
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event))
{
return true;
}
return onTouchEvent(event);
}
首先,满足以下3个条件,方法直接返回true
mOnTouchListener != null
(mViewFlags & ENABLED_MASK) == ENABLED
mOnTouchListener.onTouch(this, event)
mOnTouchListener != nullmOnTouchListener变量在View.setOnTouchListener()方法里赋值。(mViewFlags & ENABLED_MASK) == ENABLED,该条件是判断当前点击的控件是否enable。mOnTouchListener.onTouch(this, event),回调控件注册Touch事件时的onTouch(),需手动复写设置。还记得开头我们提到的:“View.OnTouchListener中的onTouch返回true,那么View.OnClickListener是绝对不会响应”这个问题嘛?
如果3个条件都满足,那么方法直接返回,之后的onTouchEvent方法便不会再执行。而onTouchEvent中最核心的逻辑,就是执行用户在View.OnClickListener定义的方法:
/**
* Touch事件的具体实现方法
*
* @param event The motion event.
* @return 返回true,此事件被消费,返回false,则没被消费
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
........
//对View是否可以消费点击事件做判断,是否设置点击事件,是否可点击
if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP://手指抬起up事件
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true);//Button按压状态变化通知
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();//去除长按状态
//执行点击事件
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();//消费点击事件
}
}
}
.......
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN://手指按下down事件
......
break;
case MotionEvent.ACTION_CANCEL://事件取消
setPressed(false);
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE://手指移动move事件
final int x = (int) event.getX();
final int y = (int) event.getY();
.......
break;
}
return true;
}
return false;
}
总结
总的来说,这是一个“层层分发,上层给下层,下层给下下层,如果下层搞不定,自己再处理的过程”。希望能帮助大家对Android的事件分发机制有了一个大致的了解。