Android事件分发流程源码解析一
Android事件分发流程源码解析二及总结
Android滑动冲突解决方案内外部拦截法及原理
在前面文章中介绍了事件如何一步步从activity传递到需要消费的View。最终调用View的dispatchTouchEvent方法。
一View的事件分发和消费
首先查看View当中dispatchTouchEvent方法源码
class:View
public boolean dispatchTouchEvent(MotionEvent event) {
//...省略
//如果窗口没有被遮盖
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//**主要看这段代码
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result为false调用自己的onTouchEvent方法处理,何时result为false?
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
相比于ViewGroup,View的事件分发要简单很多,返回结果为true则表示消费了事件,重点查看加了注释的段落,何时才会调用onTouchEvent方法?
- 1.没有设置OnTouchListener
- 2.设置了OnTouchListener并且onTouch方法返回false
接下来查看onTouchEvent的源码:
class View:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//1.如果View是设置成不可用的(DISABLED)仍然会消费点击事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
...
//2.CLICKABLE 和LONG_CLICKABLE只要有一个为true就消费这个事件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//3.在ACTION_UP方法发生时会触发performClick()方法
performClick();
}
}
}
...
break;
}
...
return true;
}
return false;
}
从上述代码可看出:View是disabled状态,不会影响事件的消费。只要CLICKABLE和LONG_CLICKABLE有一个为true,就一定会消费这个事件,就是onTouchEvent返回true。
上述为此就将事件分发整个流程介绍完了,源码细节较多,后面章节会进行总结。
二 View关于点击事件的一些源码细节
场景:为button设置一个setOnTouchListener和clickListener
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
e("MotionEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
e("MotionEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
e("MotionEvent: ACTION_UP");
break;
}
return false;
}
});
}
public void click(View v) {
e("点击了按钮");
}
大家猜想,点击事件监听是在ACTION_UP之前还是之后呢?
下面是在手指在屏幕滑动后的打印
为何click方法在ACTION_UP(手指抬起)之后打印?
通过源码我们可以看见点击事件是这样被调用的
- 在View的onTouchEvent()方法当中ACTION_UP时会依次调用如下方法:performClickInternal()->performClick()->
li.mOnClickListener.onClick(this);
思考:如果上述button的setOnTouchListener返回true,点击事件还会响应么?
答案是不会
在上述介绍中知晓了点击事件是在onTouchEvent方法中调用
onTouchEvent方法在何调用呢?我们查看上述View的事件分发dispatchTouchEvent源码,在代码进入onTouchEvent之前result需要为false才会调用。
如果我们设置了setOnTouchListener并且返回true,该那么reslut此时就会为true。就不会调用onTouchEvent方法,自然就不会响应点击事件。
三 总结
上述就是事件分发的整个过程了,源码细节比较多,确实比较难理解或记忆。下面对其进行总结,加深我们的理解和记忆,以便为后序解决事件冲突做准备。
- 事件类型
- 事件分发
ViwGroup到View事件分发的举例:老板分发任务–>总经理–>部分经理–>员工
一个任务由老板分发下去,正常情况一层层分发到员工由员工来做,员工做不了,员工交还给他的上级部门经理做,部门经理做不了就返回给总经理做。拦截的情况就是分发下来的时候,部门经理(或者总经理)直接处理,这样员工就接收不到该事件了, 部门经理处理不了,再交还给其上级处理。
- 分发拦截消费三个方法的伪代码
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消费
if (onInterceptTouchEvent(ev)){//调用onInterceptTouchEvent判断是否拦截事件
consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
}else{
consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
}
return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
}
- ViewGroup和View之间调用三个方法的流程图:
至此事件分发就介绍完了,后续章节将继续介绍如何来处理滑动冲突
Android滑动冲突解决方案内外部拦截法及原理
参考:https://www.jianshu.com/p/238d1b753e64