
事件序列解析
所谓的安卓事件是什么?具体来讲的就是点击和滑动两个操做;抽象着来讲就是下面的表格。web
MotionEvent/事件类型 | 具体操做 |
---|---|
ACTION_DOWN | 点下View |
ACTION_UP | 抬起View |
ACTION_MOVE | 滑动View |
ACTION_CANCEL | 非人为因素取消 |
事件序列通常组成:微信
点击的事件组成就是:Down --> Up编辑器
滑动的事件组成就是:Down --> Move --> Move .... --> Up函数
事件分发
-
使用到的函数 -
dispatchTouchEvent():用于事件分发 -
onTouchEvent():消费事件 -
onInterceptTouchEvent():判断是否拦截事件,仅存在于ViewGroup -
分发对象 -
Activity -
ViewGroup -
View
Activity的事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
// 从判断语句中能够得出全部事件的起点就是Down
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 实现屏保功能
onUserInteraction();
}
// 向上传递至ViewGroup,调用其dispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
文章中我已经添加了注释内容,其中getWindow()
得到就是一个Window
抽象类,根据其子类PhoneWindow
咱们能够很容易得知最后调用的其实就是ViewGroup
的dispatchTouchEvent()
方法布局
/**
* 实际上就是判断事件是不是DOWN事件,event的坐标是否在边界内等
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
最后就是Activity
中的onTouchEvent()
方法了,这个模块干的事情在注释中也就很清晰明了了。post
ViewGroup的事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
// ········
// 初始化Down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 丢弃以前手头上干的事情,从新开始响应Down事件
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查是否须要拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 这个与运算是用于影响除Down之外的事件的
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 {
// 当前按压的位置没有控件,或者当前控件并不可被点击,直接被ViewGroup拦截
intercepted = true;
}
// ········
/**
*这个判断里面一样的仍是判断响应的事件,而后就是经过一个for循环判断位置来判断当前的子控件是否在对应的位置内
* 还有很是重要的一点就是这个循环的判断仍是倒叙的
*/
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked ==MotionEvent.ACTION_HOVER_MOVE) {
········
if (newTouchTarget == null && childrenCount != 0) {
········
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
// 后面的篇幅主要用于判断当前的控件的各类属性是不是知足须要的。好比说位置、是否能够点击、是否隐藏等一系列信息
········
}
········
}
倒序是为了什么呢? 这个问题一样是一个开发过程当中常见,可是却很容易被忽略的问题。this
你是否有见过这样的一段代码,若是是ButtonA
和ButtonB
这两个按钮是在同一个位置出现,ButtonA
略大于ButtonB
,也就是下图所示url
对应在
XML
布局文件中的代码通常相似于下面这段。spa
<Layout>
<Button id="A"/>
<Button id="B"/>
</layout>
若是出现点击事件发生在ButtonA
上时,只要它有足够的能力势必会被ButtonA
所消费。可是若是时出如今ButtonB
的区域呢? 这也就体现了倒序去进行事件消费的做用了,若是正序去进行消费的话,那这个事件最后的消费者必定是ButtonA
,而咱们须要的实际效果是ButtonB
在有足够能力消费时交由它进行完成。.net
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
由onInterceptTouchEvent(MotionEvent ev)
函数可知,默认其实并不会去拦截。因此就通常状况而言,dispatchTouchEvent()
方法是须要去循环遍历子控件集合去寻找对应的控件的。
使用一个伪代码解释以上的逻辑
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){
// 本身拦截,本身消费
onTouchEvent(ev);
}else{
// 不拦截,分发给子View进行消费
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
View事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
// ·····
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
// ·····
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return result;
}
经过以往的实践,咱们知道只有经过设置了监听器的View才可以去监听事件,那么在dispatchTouchEvent()
方法中也是同样的,若是View并无被设置监听器,变量result
也不会被赋值成为true。
从代码中很容易看出
onTouch()
方法的优先级大于onTouchEvent()
方法。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// ·····
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// ·····
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
// ·····
}
// ·····
mIgnoreNextUpEvent = false;
break;
// ·····
}
return true;
}
return false;
}
在onTouchEvent()
方法中其实具体干了一件事情,那就是区别究竟是长按事件仍是点击事件。
那么先行判断的是长按事件仍是点击事件呢?答案很明显,在代码行中removeLongPressCallback();
有一个这样的函数,这就是去除长按事件回调的函数,因此答案就是长按事件是第一个被判断的事件,而后才是点击事件。
判断这个方法的事件的方法就是经过作出Up动做时的时间和作出Down动做时的时间间隔。若是Down和Up两个动做之间的时间间隔小于500ms,就是点击事件。
总结

本文分享自微信公众号 - 告物(ClericYi_Android)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。