《Android》事件传递过程
1、什么是事件传递?
Android事件传递是指用户操作屏幕产生的一系列动作事件(按下、滑动、抬起)从外层传递到的内层的过程。
2、外层到内层如何理解?
Activity —> Window ----> ViewGroup ---->View
3、必须了解的基础
3.1 首先要知道传递的对象是一个MotionEvent类的对象。这个类中定义了动作常量,比如“按下” public static final int ACTION_DOWN = 0等等。
3.2 第二要知道3个重要的方法 :
boolean dispatchTouchEvent(MotionEvent ev) 分发事件ev
boolean onInterceptTouchEvent(MotionEvent ev) 是否拦截事件ev
boolean onTouchEvent(MotionEvent ev) 是否消耗事件ev
3.3经常给view设置的有关事件监听的方法
view.setOnTouchListener(…) 触摸事件监听
view.setOnClickListener(…) 单击事件监听
4、事件传递的具体过程
4.1产生事件:
手指触碰屏幕首先产生一个MotionEvent Down事件,随着手指滑动产生多个MotionEvent Move事件,最后抬起手指产生一个MotionEvent Up事件
4.2 事件在Activity中:
Activity中有dispatchTouchEvent()、onTouchEvent()2个方法。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
dispatchTouchEvent中事件先去传给getWindow().superDispatchTouchEvent(ev)去做判断,如果返回为true,则直接结束。如果为false 触发Activity的onTouchEvent()方法。
也就是说在Activity中通过getWindow().superDispatchTouchEvent(ev)方法将事件传递给了Window。
4.3 事件在Window中:
getWindow().superDispatchTouchEvent(ev)调用Window内部的superDispatchTouchEvent()方法。
public abstract boolean superDispatchTouchEvent(MotionEvent event);
这是一个抽象方法。具体逻辑在Window的唯一继承类PhoneWidow中:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
事件在window中没有做任务处理,直接通过mDecor.superDispatchTouchEvent(event)传递给了DecorView。DecorView是view的最顶层,被定义在PhoneWindow中的一个对象。它是一个继承自FrameLayout的ViewGroup。
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
4.4 事件在ViewGroup中:
事件传递到DecorView的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
调用了super.dispatchTouchEvent(event),这个super其实就是ViewGroup。
在ViewGroup中有dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()3个方法。
由于源码过长,其中还包含很多控制单元,为了分析过程直接贴出来不直观,不想篇幅过长,自己去查看印象更深刻!直接总结:
(1)事件MotionEvent传递给dispatchTouchEvent(ev)进行事件分发
(2)在分发方法内部调用了onInterceptTouchEvent(ev)判读是否对事件拦截(返回false代表不拦截,true表示拦截。默认为false)。
(3) onInterceptTouchEvent(ev)返回true, 说明要拦截事件,ev会交给此viewGroup处理。
(4)如果viewGroup设置了OnTouchListener,onTouch()会被调用,onTouch()返回false viewGroup的onTouchEvent会被调用。onTouch()返回true viewGroup的onTouchEvent会被屏蔽掉。
(5)onInterceptTouchEvent(ev)返回false,不拦截事件,将会把事件传递给 子View(view或viewGroup)的dispatchTouchEvent(ev)处理。如果子view是ViewGroup则继续从(1)开始循环。
4.4 事件在View中:
在View中有dispatchTouchEvent()、onTouchEvent()2个方法。没有onInterceptTouchEvent()
与ViewGroup过程相似,毕竟ViewGroup继承自View。ViewGroup也没有重写onTouchEvent,所以ViewGroup中onTouchEvent()方法就是View中的方法。
(1)事件MotionEvent传递给dispatchTouchEvent(ev)进行事件分发
(2)如果view设置了OnTouchListener,onTouch()会被调用,onTouch()返回false view的onTouchEvent会被调用。onTouch()返回true view的onTouchEvent会被屏蔽掉。
5、setOnTouchListener()\setOnClickListener()\onTouchEvent()调用关系:
下面是view的dispatchTouchEvent()源码
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
其中判断了mOnTouchListener != null && mOnTouchListener.onTouch(this, event)条件,之后根据返回结果判断是否去调用onTouchEvent(event)。验证了OnTouchListener 调用优先,并控制onTouchEvent()的调用。
在view的onTouchEvent()方法中
public boolean onTouchEvent(MotionEvent event) {
...代码省略...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...代码省略...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
...代码省略...
}
...代码省略...
performClickInternal()方法会调performClick(),贴出performClick()源码
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
这里触发了li.mOnClickListener.onClick(this)。说明点击事件onClick是在onTouchEvent中间接调用的。
6、事件交由上层处理
子视图不能处理事件时候,onTouchEvent 返回false,事件将交给父视图的onTouchEvent调用。如果还不能处理事件则依次往上传递。直到Activity。也就是在getWindow().superDispatchTouchEvent(ev)返回了false。将调用Activity的onTouchEvent()方法。
作者:calmerman
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341