Android事件传递和分发机制

PS:之前写的有些地方不够清楚,配图也有点小问题,特此更正了下。2017-9-18

今天看了Android事件分发的一些资料,自己也研究了源码,了解了事件分发的流程和机制,特此记录下。
首先自定义了两个View,一个Button,一个LinearLayout,分别重写了onTouchEvent、dispatchTouchEvent、onFilterTouchEventForSecurity这三个方法。代码如下:

public class MyButton extends AppCompatButton {
    private static final String TAG = "TestTouchEventButton";
    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: "+event.getAction());
//        super.onTouchEvent(event);
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "dispatchTouchEvent: "+event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        Log.i(TAG, "onFilterTouchEventForSecurity: "+event.getAction());
        return super.onFilterTouchEventForSecurity(event);
    }

}

public class MyLayout extends LinearLayout {
    private static final String TAG = "TestTouchEventLayout";

    public MyLayout(Context context) {
        super(context);
    }

    public MyLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: "+event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: "+ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        Log.i(TAG, "onFilterTouchEventForSecurity: ");
        return super.onFilterTouchEventForSecurity(event);
    }
}
//activity 
public class TestTouchDispatchActivity extends BaseActivity {

    private static final String TAG = "TestTouchEventActivity";

    @BindView(R.id.btn)
    MyButton mBtn;


    @Override
    protected void initPresenter() {

    }

    @Override
    protected int attachLayoutRes() {
        return R.layout.activity_test_touch_dispatch;
    }

    @Override
    protected void initViews() {
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick: ");
            }
        });

        mBtn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG, "onTouch: "+event.getAction());
                return false;
            }
        });
    }
}
//布局文件
<com.sleep.sunshine.view.MyLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.sleep.sunshine.view.MyButton
        android:id="@+id/btn"
        android:text="CLICK"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</com.sleep.sunshine.view.MyLayout>
一、先屏蔽掉MyLayout中的log,只看Button里面的事件分发和传递情况(PS:每项对比后直接说正确的结论,后面在从源码层面分析):
1.onTouch方法里面返回true和false,打印log如下:
mBtn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG, "onTouch: "+event.getAction());
                return true/false;
            }
        });
//true
07-27 18:45:56.160 16726-16726/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:45:56.160 16726-16726/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:45:56.160 16726-16726/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:45:56.277 16726-16726/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 18:45:56.277 16726-16726/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 18:45:56.277 16726-16726/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
//false
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
07-27 18:43:04.373 14262-14262/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 18:43:04.373 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 18:43:04.374 14262-14262/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
07-27 18:43:04.374 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
07-27 18:43:04.374 14262-14262/com.sleep.sunshine I/TestTouchEventActivity: onClick: 

# 对比结论:onTouch返回true后,不会再调用onTouchEvent和onClick方法。
2.将MyButton里面的onTouchEvent方法里面返回分别返回true和false,打印log如下:
@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: "+event.getAction());
        return true/false;
    }
//true
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
//false
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0

# 对比结论:a、onTouchEvent返回false,代表不再接受同组的事件(ACTION_DOWN= 0 ,ACTION_UP=1)
#b、和上面的1对比说明onClick方法是在super.onTouchEvent(event)方法里面调用的。事实证明查看源码也能发现这一点:
//父类TextView的onTouchEvent方法。
 @Override
    public boolean onTouchEvent(MotionEvent event) {
       ......
     
            final boolean textIsSelectable = isTextSelectable();
            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
                // The LinkMovementMethod which should handle taps on links has not been installed
                // on non editable text that support text selection.
                // We reproduce its behavior here to open links for these.
                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
                        getSelectionEnd(), ClickableSpan.class);

                if (links.length > 0) {
                    links[0].onClick(this);
                    handled = true;
                }
            }

          ......

        return superResult;
    }
3.将MyButton里面的onFilterTouchEventForSecurity方法里面返回分别返回true和false,打印log如下:
@Override
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        Log.i(TAG, "onFilterTouchEventForSecurity: "+event.getAction());
        return true/false;
    }
//true
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
07-27 19:13:40.083 32428-32428/com.sleep.sunshine I/TestTouchEventActivity: onClick: 
//false
07-27 19:15:31.465 1655-1655/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 19:15:31.467 1655-1655/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
4.将MyButton里面的dispatchTouchEvent方法里面返回分别返回true和false,打印log如下:
@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "dispatchTouchEvent: "+event.getAction());
        return true/false;
    }
//true
07-27 19:19:09.215 4026-4026/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 19:19:09.264 4026-4026/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
//false
07-27 19:19:46.849 4676-4676/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0

#结论:View 中的dispatchTouchEvent通过开关onFilterTouchEventForSecurity(低版本的API是用的onInterceptTouchEvent)里面控制着onTouch和onTouchEvent的调用,而且可以看到当onTouch返回true的时候,就不会调用onTouchEvent方法了,证明了1中的现象。
  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;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
       .....
        return result;
    }
button事件分发小节:
2428413-fc026fbb6243cad7.png
二、在有父布局Layout的情况下的事件分发直接说结论了,有兴趣的同学可以参考上面的方法去打印Log或者翻看源码。
1、不做任何改变时的log
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity: 
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity: 
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 2
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity: 
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
09-18 10:09:45.397 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onClick: 
2、将MyButton的onFilterTouchEventForSecurity 方法里直接返回false
@Override
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        Log.i(TAG, "onFilterTouchEventForSecurity: "+event.getAction());
//        return super.onFilterTouchEventForSecurity(event);
        return false;
    }

09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity: 
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity: 
09-18 10:13:33.889 1386-1386/com.sleep.sunshine I/TestTouchEventActivity: onTouch  layout: 0
09-18 10:13:33.889 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: onTouchEvent: 0
3、将MyLayout的onFilterTouchEventForSecurity 方法里直接返回false
09-18 10:16:46.071 4555-4555/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:16:46.071 4555-4555/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity: 
3、将MyLayout的dispatchTouchEvent方法里返回true / false
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: "+ev.getAction());
//        return super.dispatchTouchEvent(ev);
        return true/false;
    }

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

推荐阅读更多精彩内容