Android 事件分发机制源码

Android 事件分发机制源码

  • Android,事件机制,Android事件分发机制源码

Android Touch 事件机制。有很多通过 Log 输出的方式去分析 Android Touch 事件的分发机制,我这里是通过阅读源代码的方式来分析。

Note:这里贴出的代码有所删减。

View 的分析

从 setOnTouchListener 入手

我是以逆推的方式来分析。跟 Touch 有关的,首先想到是 setOnTouchListener。

public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}

getListenerInfo() 是获取一个 ListenerInfo 类的实例,咱就不用关心它是怎么设计,怎么实现的,咱们就关心 mOnTouchListener 是怎么使用的。

dispatchTouchEvent

然后在 dispatchTouchEvent(MotionEvent) 里找到了 mOnTouchListener 的使用。

public boolean dispatchTouchEvent(MotionEvent event){
    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;
    }
}

在 dispatchTouchEvent() 方法里,如果 mOnTouchListener 不为空,就调用 mTouchListener.onTouch() 方法,如果返回 true ,那么 dispatchTouchEvent 也就返回 true . 如果 listener 返回 false ,那么就调用 onTouchEvent() 方法。咱们再去看看 onTouchEvent() 方法。

onTouchEvent

onTouchEvent(MotionEvent) 方法比较长,我就不贴那么多代码。看方法的名字大概能猜出这段代码主要就是判断 View 是否获得焦点,是否需要触发 click 事件,或者 long click 事件,还有就是 setPressed 的状态等等。

public boolean onTouchEvent(MotionEvent event){
    requestFocus();
    setPressed(true/false);
    performClick();
}

这里就是执行 click 事件。

public boolean performClick() {
    mOnClickListener.onClick(this);
}

总结

一个 View 的 Touch 事件从 dispatchTouchEvent() 方法开始,如果这个 View 有 OnTouchListner ,则先调用 OnTouchListener 的回调,如果回调返回 true,则事件终止,如果事件返回 false,则调用 View 的 onTouchEvent() 方法。在 onTouchEvent() 方法有可能会触发 OnClickListener 的回调。

ViewGroup 的分析

dispatchTouchEvent

上面分析的是 View , View 的事件从哪来,肯定是从它的容器来,ViewGroup 就是所有容器的父类,咱们就去看看 ViewGroup 是怎么处理的。首先找到的是 dispatchTouchEvent(MotionEvent)

public boolean dispatchTouchEvent(MotionEvent ev) 
final boolean intercepted;
intercepted = onInterceptTouchEvent(ev);
if(!intercepted){
    for(children){
        dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
    }
}

ViewGroup 的 dispatchTouchEvent() 方法首先调用的是 onInterceptTouchEvent(MotionEvent) 方法。如果 onInterceptTouchEvent() 返回 false,那么就遍历子控件,也就是 children 。

在 dispatchTransformedTouchEvent 里,如果 child 不为空,那么调用 dispatchTouchEvent(MotionEvent) 方法。如果 child 是 View ,那么就执行上面咱们分析的逻辑,如果是 ViewGroup ,那么就执行咱们现在分析的。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)

    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }

onInterceptTouchEvent

在ViewGroup 里,onInterceptTouchEvent(MotionEvent) 很简单的只返回了 false.如果你自定义了一个ViewGroup,看你的需求重写这个方法。

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

在 ViewGroup 里没有找到 onTouchEvent 的方法,但是ViewGroup 继承于 View,所以,ViewGroup 也继承了 onTouchEvent 方法。

总结

ViewGroup 的 Touch 事件也是从 dispatchTouchEvent 方法开始。会先调用 onInterceptTouchEvent() 方法,如果返回 true,表示事件被截住,不会传递给 children;如果返回 false,则事件会传递给 children。Children 可能是 View ,也可能是ViewGroup,但都会调用它们的 dispatchTouchEvent 方法。

Activity

ViewGroup 的 Touch 事件从哪来呢?应该是从 Activity 里来,咱们去看看 Activity 的代码。在Activity 里,发现了onTouchEvent(MotionEvent) 和 dispatchTouchEvent(MotionEvent) 方法,但是很不幸,它们都交给了一个叫 mWindow 的对象去处理了。不过可以知道的是 onTouchEvent 是在 dispatchTouchEvent() 里被调用的,这个设计跟 View/ViewGroup 是一致的。

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

总结

Activity 的 touch 事件都交给了 Window 处理。

Window

另外有句废话,通过看源码,你会发现,mWindow 有时候是直接使用,有时候是通过 getWindow() 获得的。好吧,怎么不关心他们的代码写得怎么样,咱们关心怎么实现的就行。Activity 的 dispatchTouchEvent() 方法调用的是 mWindow 的superDispatchTouchEvent() 方法, mWindow 是 Window 的一个实例,Window 又是一个抽象类。

public abstract class Window

不过在 Window 类的注释上,咱们发现了这么一段话,大概意思是说 Window 有一个实现类 android.policy.PhoneWindow .

The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window.

总结

Window 是一个抽象类,并没有直接处理 Activity 交给它的 touch 事件,而是 Window 的子类 PhoneWindow 实现了 touch 事件的处理。

PhoneWinow

PhoneWinow 其实在 com.android.internal.policy.impl 包里,找到 PhoneWindow 后,咱们找 superDispatchTouchEvent() 方法。

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

mDecor 的定义

PhoneWindow 调用 mDecor 的 superDispatchTouchEvent 方法。mDecor 是什么玩意?mDecor 是 DecorView 的一个实例。

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;

PhoneWindow.DecorView

PhoneWindow 定义了一个 DecorView 的实例mDecor,同时也定义了一个 ViewGroup 的实例 mContentParent,这个 ViewGroup 一会会用到。那么又是什么玩意呢?它是PhoneWindow 的一个内部类,继承于 FrameLayout ,也就是它是一个 View,也是一个 ViewGroup。

private final class DecorView extends FrameLayout

mDecor 的实例化

那么 mDecor 在什么时候创建呢?咱们找到了一个叫 installDecor() 的方法,这里实例化了 mDecor ,也实例化了 mContentParent .

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    }
}

generateDecor() 的方法很简单,就是 new 了一个 DecorView 对象。

protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

generateLayout 的方法就很复杂,其实也是实例化了一个 FrameLayout.代码我就不全贴出来了。

protected ViewGroup generateLayout(DecorView decor){
    mDecor.startChanging();
    
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
    mDecor.finishChanging();

    return contentParent;
}

在 generateLayout 方法里,inflate 了一个 View ,并且把它加入到 mDecor(一个FrameLayout) 里。然后通过 findViewById() 方法,获取到一个 ViewGroup ,也就是最终的 mContentParent .mDecor.startChanging() 和 mDecor.finishChanging() 的作用是什么呢?鬼知道它们是干啥的,我贴出来就是想让大家看看,一会用 mDecor ,一会用 decor,这是几个意思?!

mDecor 和 mContentParent 的关系

mDecor 和 mContentParent 的关系还不是很清晰,到底啥关系?现在咱们要去看看 findViewById() . PhoneWindow 没有这个方法,在 Window 里找到了。

public View findViewById(int id) {
    return getDecorView().findViewById(id);
}

它调用 getDecorView().findViewById(id) ,那么咱们找 getDecorView()。

public abstract View getDecorView();

结果是一个抽象方法,那么咱们再回去 PhoneWindow 找,返回的是 PhoneWindow 的 mDecor。

@Override
public final View getDecorView() {
    if (mDecor == null) {
        installDecor();
    }
    return mDecor;
}

也就是说,mContentParent = mDecor.findViewById(id),这样就可以理解 mContentParent 和 mDecor 的关系了。但是它们俩的关系跟咱们 touch 事件有毛关系啊?

DecorView.superDispatchTouchEvent(MotionEvent event)

咱们从 Activity 的 dispatchTouchEvent 一路追到 mDecor 来了,Activity 的 dispatchTouchEvent 其实就是调用 mDecor.superDispatchTouchEvent(event), DecorView 的 superDispatchTouchEvent 的实现如下。

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

DecorView 与 setContentView()

DecorView 的 super 就是 FrameLayout , FragmentLayout 是 ViewGroup 的子类,也就是咱们上面分析的。然后咱们写的布局 Layout 是怎么跟 mDecor 扯上关系的。咱们去看看 setContentView()。下面是 Activity 的代码。

public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initActionBar();
}

Window 的 setContentView(int) 代码,是一个抽象方法,咱们去 PhoneWindow 找。

public abstract void setContentView(int layoutResID);

setContentView() 的最终实现

PhoneWindow 的 setContentView(int) 代码。这里是 Activity.setContentView 的最终实现。

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } 
    mLayoutInflater.inflate(layoutResID, mContentParent);
}

installDecor() 方法就是实例化了 mDecor 和 mContentParent , mDecor 是 mContentParent 的父容器,mContentParent 又是咱们通过 setContentview(int) 加载的 layout 的父容器。

所以,Activity 获取的 touch Event 通过 dispatchTouchEvent 最终交给了 mDecor 处理,mDecor 又是一个 View,它会调用它的 dispatchTouchEvent() 去处理分发 Touch Event。

Activity 的 onTouchEvent() 方法

Activity 的 onTouchEvent() 方法最终也交给 PhoneWindow 的 DecorView , DecorView 继承于 FrameLayout ,重写了 onTouchEvent() 方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
    return onInterceptTouchEvent(event);
}

在 onTouchEvent() 方法里,直接调用 onInterceptTouchEvent(). onInterceptTouchEvent() 的代码我就不贴了,因为我没有看懂,大概就是对 Touch 事件的 X、Y 轴做边界检查。

总结

  • Touch 事件从 Activity 的 dispatchTouchEvent() 开始,然后交给 View/ViewGroup 的 dispatchTouchEvent() 。如果 View/ViewGroup 的 dispatchTouchEvent() 没有消费,就交给Activity 的 onTouchEvent() 。

  • ViewGroup 的 dispatchTouchEvent() 会先调用 onInterceptTouchEvent().如果 ViewGroup 的 onInterceptTouchEvent() 返回 true ,事件就不交给 ViewGroup 的 Children。如果 onInterceptTouchEvent() 返回 false,那么事件就继续交给 Children 的 dispatchTouchEvent()。

  • View 的 dispatchTouchEvent() 会先调用 View 的 OnTouchListener 的回调,如果没有 OnTouchListener 或者 OnTouchListener 没有消费,就交给 OnTouchEvent() 方法。

该文章由 Binkery 发布于 Binkery技术博客 如转载请注明出处,该文章的链接地址为 https://binkery.com/archives/470.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容