Android事件转发机制—源码分析(一)
2017-05-25 15:03
369 查看
这是小王君熬夜写的博客,虽然很晚,还是坚持写下去吧!(注:本文是以ViewGroup的点击事件的处理为例来分析)
一,在分析源码前,我们先总结下事件转发机制的一些简单结论:
1,当用户开始触摸activity时,Activity调用diaptcherTouchEvent开始转发,会一层层的向下进行转发,也就是每个View都会调用diaptcherTouchEvent方法。这里需要注意的是,ViewGroup在将事件转发给子view的时候,根据子view添加进ViewGroup时间的逆序排列转发事件,这是android框架的内部机制实现的。
2,当事件分发到最后一个子View的时,会以从下往上的顺序调用onTouchEvent方法。如果哪个view对这个事件感兴趣,就处理该事件,执行onTouchEvent里面的代码,如果感兴趣了还返回true,那么事件就被消费了,被吃了就木有啦,上层view的onTouchEvent就不会再接收事件了;否则一直将事件传递给上层的view的onTouchEvent,直到最后Activity的onTouchEvent收场处理。
3,在结论1中,如果在事件转发的中间,某一个view对事件感兴趣中途进行拦截,会调用该view的onInterceptTouchEvent方法,若该方法返回true,就会调用该view的onTouchEvent方法对事件进行处理,就不会再往下转发事件了,否则会一直转发下去。
这里,我们画一个流程图给大家看,如下:
Activity(diaptcherTouchEvent)→ViewGroup(diaptcherTouchEvent) →........→ 子View(diaptcherTouchEvent)
↓ 拦截,onInterceptTouchEvent
Activity(onTouchEvent) ← ViewGroup(onTouchEvent)
←........← 子View(onTouchEvent)
源码分析入口,首先进入Activity类查阅如下:
Activity.class中:
[java] view
plain copy
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
分析:事件触摸是封装在MotionEvent中进行传递;
Activity转发的时候,先交给Activity所附属的Window,于是调用PhoneWindow的superDispatchTouchEvent()方法。
接下来,我们进入PhoneWindow类里进行查阅如下:
注:类PhoneWindow继承了Window ,Window只有这一个子类; PhoneWindow.class在framework源码中。
[java] view
plain copy
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
分析:mDecor 就是DecorView,PhoneWindow将事件传递交给DecorView处理。
接下来,我们进入DecorView类进行查阅如下:
[java] view
plain copy
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
分析:DecorView将事件处理交给了super爸爸,这个爸爸就是FrameLayout,爸爸继承爷爷ViewGroup,实际就是ViewGroup的dispatchTouchEvent()。至于DecorView如何将事件转发到xml中布局的根view,这里不作分析,反正肯定是可以转发过来的,因为我们写的xml布局是能接受触摸事件的。这里的根view,就是setContentView中最外层的view,一般都是一个组控件ViewGroup。
接下来,我们要开始查阅ViewGroup的源码,如下:
这里主要看注释的核心代码,能梳理清楚事件转发的流程即可,不必理解每一行代码
[java] view
plain copy
public abstract class ViewGroup extends View implements ViewParent, ViewManager {..
public boolean dispatchTouchEvent(MotionEvent ev) {
//...code 表示此位置代码有省略,这里只展示核心代码
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//Viewgroup拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//...code
//intercepted为false,Viewgroup不拦截时,事件转发到子View的代码执行如下:
if (!canceled && !intercepted) {
//...code
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//若子View处理点击事件,会给下面两个变量赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
//...code
}
//...code
//ViewGroup拦截下事件或子View的onTouchEvent(..)返回false时,点击事件交给ViewGroup处理
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//ViewGroup处理点击事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
//...code
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//...code
//child为空时,ViewGroup处理点击事件
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
//...code
}
}
分析:super.dispatchTouchEvent(event)调用爸爸的方法
接下来,我们继续查阅View.class
[java] view
plain copy
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
分析:
li != null && li.mOnTouchListener != null为防止空指针,代码健壮性更强;
主要看后面两个:
一 ,(mViewFlags & ENABLED_MASK) == ENABLED 为true时指控件可clickable。
二 ,li.mOnTouchListener.onTouch(this, event) 回调接口的方法(由用户调用view.setOnTouchListener(..)实例化监听器接口);
当onTouch(..)返回true(四个条件都为true时),在方法onTouch(..)中处理点击事件。
当onTouch返回false时(或者四个条件任一为false),执行该View自己的onTouchEvent(event)处理该点击事件。
如果事件处理了,不用给Activity的onTouchEvent()收场处理,否则由Activity的onTouchEvent()收场处理事件。
总结一下转发流程经过的窗口和view:
Acticity——>PhoneWindow——>DecorView——>—>根view—>..........—>最后一个子view
注:根view,也就是setContentView(R.layout.XX)中的布局
下图是用Eclipse的Hierarchy View查看某一页面的布局结构,其实每一个布局结构的根都是PhoneWindow$DecorView。
到这里,源码分析就结束了。我们想进步,还是需要读者自己去翻开源码查阅,才能更深刻理解事件分发机制。
辛苦大家能看到这里,文章有不够准确的地方,期待大家能在下面评论中指出,一起学习进步!!!
———小王君 (*^__^*)
一,在分析源码前,我们先总结下事件转发机制的一些简单结论:
1,当用户开始触摸activity时,Activity调用diaptcherTouchEvent开始转发,会一层层的向下进行转发,也就是每个View都会调用diaptcherTouchEvent方法。这里需要注意的是,ViewGroup在将事件转发给子view的时候,根据子view添加进ViewGroup时间的逆序排列转发事件,这是android框架的内部机制实现的。
2,当事件分发到最后一个子View的时,会以从下往上的顺序调用onTouchEvent方法。如果哪个view对这个事件感兴趣,就处理该事件,执行onTouchEvent里面的代码,如果感兴趣了还返回true,那么事件就被消费了,被吃了就木有啦,上层view的onTouchEvent就不会再接收事件了;否则一直将事件传递给上层的view的onTouchEvent,直到最后Activity的onTouchEvent收场处理。
3,在结论1中,如果在事件转发的中间,某一个view对事件感兴趣中途进行拦截,会调用该view的onInterceptTouchEvent方法,若该方法返回true,就会调用该view的onTouchEvent方法对事件进行处理,就不会再往下转发事件了,否则会一直转发下去。
这里,我们画一个流程图给大家看,如下:
Activity(diaptcherTouchEvent)→ViewGroup(diaptcherTouchEvent) →........→ 子View(diaptcherTouchEvent)
↓ 拦截,onInterceptTouchEvent
Activity(onTouchEvent) ← ViewGroup(onTouchEvent)
←........← 子View(onTouchEvent)
源码分析入口,首先进入Activity类查阅如下:
Activity.class中:
[java] view
plain copy
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
分析:事件触摸是封装在MotionEvent中进行传递;
Activity转发的时候,先交给Activity所附属的Window,于是调用PhoneWindow的superDispatchTouchEvent()方法。
接下来,我们进入PhoneWindow类里进行查阅如下:
注:类PhoneWindow继承了Window ,Window只有这一个子类; PhoneWindow.class在framework源码中。
[java] view
plain copy
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
分析:mDecor 就是DecorView,PhoneWindow将事件传递交给DecorView处理。
接下来,我们进入DecorView类进行查阅如下:
[java] view
plain copy
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
分析:DecorView将事件处理交给了super爸爸,这个爸爸就是FrameLayout,爸爸继承爷爷ViewGroup,实际就是ViewGroup的dispatchTouchEvent()。至于DecorView如何将事件转发到xml中布局的根view,这里不作分析,反正肯定是可以转发过来的,因为我们写的xml布局是能接受触摸事件的。这里的根view,就是setContentView中最外层的view,一般都是一个组控件ViewGroup。
接下来,我们要开始查阅ViewGroup的源码,如下:
这里主要看注释的核心代码,能梳理清楚事件转发的流程即可,不必理解每一行代码
[java] view
plain copy
public abstract class ViewGroup extends View implements ViewParent, ViewManager {..
public boolean dispatchTouchEvent(MotionEvent ev) {
//...code 表示此位置代码有省略,这里只展示核心代码
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//Viewgroup拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//...code
//intercepted为false,Viewgroup不拦截时,事件转发到子View的代码执行如下:
if (!canceled && !intercepted) {
//...code
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//若子View处理点击事件,会给下面两个变量赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
//...code
}
//...code
//ViewGroup拦截下事件或子View的onTouchEvent(..)返回false时,点击事件交给ViewGroup处理
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//ViewGroup处理点击事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
//...code
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//...code
//child为空时,ViewGroup处理点击事件
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
//...code
}
}
分析:super.dispatchTouchEvent(event)调用爸爸的方法
接下来,我们继续查阅View.class
[java] view
plain copy
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
分析:
li != null && li.mOnTouchListener != null为防止空指针,代码健壮性更强;
主要看后面两个:
一 ,(mViewFlags & ENABLED_MASK) == ENABLED 为true时指控件可clickable。
二 ,li.mOnTouchListener.onTouch(this, event) 回调接口的方法(由用户调用view.setOnTouchListener(..)实例化监听器接口);
当onTouch(..)返回true(四个条件都为true时),在方法onTouch(..)中处理点击事件。
当onTouch返回false时(或者四个条件任一为false),执行该View自己的onTouchEvent(event)处理该点击事件。
如果事件处理了,不用给Activity的onTouchEvent()收场处理,否则由Activity的onTouchEvent()收场处理事件。
总结一下转发流程经过的窗口和view:
Acticity——>PhoneWindow——>DecorView——>—>根view—>..........—>最后一个子view
注:根view,也就是setContentView(R.layout.XX)中的布局
下图是用Eclipse的Hierarchy View查看某一页面的布局结构,其实每一个布局结构的根都是PhoneWindow$DecorView。
到这里,源码分析就结束了。我们想进步,还是需要读者自己去翻开源码查阅,才能更深刻理解事件分发机制。
辛苦大家能看到这里,文章有不够准确的地方,期待大家能在下面评论中指出,一起学习进步!!!
———小王君 (*^__^*)
相关文章推荐
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android源码分析-点击事件派发机制
- Android 从源码角度分析事件分发机制(三)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android事件处理机制轻量级源码分析
- Android源码分析-点击事件派发机制
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析三(Activity篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- Android源码分析-点击事件派发机制
- Android触摸屏事件派发机制详解与源码分析三(Activity篇)
- [置顶] Android源码分析-点击事件派发机制
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- 卷二 Dalvik与Android源码分析 第二章 进程与线程 2.2 Dalvik线程创建机制 图书版试读--请勿转发
- Android触摸屏事件派发机制详解与源码分析三(Activity篇)
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android源码分析-点击事件派发机制
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android应用开发原理之从ViewGroup源码分析ViewGroup的事件分发机制