对两个事件分发案例的探究

来源:互联网 发布:qq语音软件下载 编辑:程序博客网 时间:2024/06/10 21:15

在开发中,遇到两个事件分发方面的应用案例,实际运行与我的预期不符。特意做了实验,探究原因,现记录如下,以备日后查阅。

  • 案例现象
  • 原因探究
    • AppCompatEditText
    • SwitchCompat
  • 总结

案例现象

抽象后的代码:

Activity:

  findViewById(R.id.tv).setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.d(TAG, "textview onTouchED");                return false;            }        });        findViewById(R.id.edittext).setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.d(TAG, "edittext onTouchED");                return false;            }        });        findViewById(R.id.rootview).setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.d(TAG, "rootview onTouchED");                return false;            }        });

xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/rootview"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <TextView        android:id="@+id/tv"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:background="@color/colorAccent"        android:text="xxxxxx"        android:textColor="@color/colorPrimaryDark"        android:textSize="20sp" />    <android.support.v7.widget.AppCompatEditText        android:id="@+id/edittext"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="20dp" />    <android.support.v7.widget.SwitchCompat        android:id="@+id/switchBtn"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="20dp" /></LinearLayout>

父 View 是 LinearLayout,有三个子 View:TextView、AppCompatEditText 和 SwitchCompat。除了 SwitchCompat,其余三个都绑定了 OnTouchListener,而且 onTouch() 方法返回 false。

运行 demo,依次点击上述控件,查看结果并得出下表:

控件 预期打印结果 实际打印结果 是否相符 TextView textview onTouchED

rootview onTouchED textview onTouchED

rootview onTouchED 是 AppCompatEditText textview onTouchED

rootview onTouchED edittext onTouchED

edittext onTouchED 否 SwitchCompat rootview onTouchED 否


预期结果基于以下原理:
子 View 上绑定了 OnTouchListener,而且返回 false,表明其不消费该事件,如果父 View 上有 OnTouchListener,那父 View 会消费该事件;如果子 View 上没有绑定 OnTouchListener,那么子 View 不会消费 OnTouch 事件,交由父 View 消费。

从表格可知,只有 TextView 的行为符合预期。AppCompatEditText 有两个地方与预期不符:

  • 父 View 没有消费 OnTouch 事件;
  • AppCompatEditText 消费了两次 OnTouch 事件(打印出两次);

对于 SwitchCompat,根据预期,由于其并未绑定 OnTouchListener,被点击时应该未消费该事件,转而由其父 View 消费,应该打印出“rootview onTouchED”,然而实际并没有。

原因探究

AppCompatEditText

对于 AppCompatEditText,通过单步调试源代码发现:

第一,AppCompatEditText#dispatchTouchEvent() 返回 true,TextView(android-25)第 8485 行:

            if (mMovement != null) {                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);            }

即,虽然我们在 OnTouchListener#onTouch() 返回了 false,但是 AppCompatEditText 实际上消费了该事件。

实际上,即使我们不对 AppCompatEditText 绑定 OnTouchListener,其 dispatchTouchEvent() 依旧返回 true,依然消费 DOWN 和 UP 事件。我推测控件消费这些事件用于乎起软键盘和输入框内文本选择。

第二,两次打印动作都是在 View#dispatchTouchEvent() 方法中进行的,分别对应 ACTION_DOWN 和 ACTION_UP ,第 10017 行:

 if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }

这个故事告诉我们,我们预期的结果仅对那些没有绑定各种 Listener 以及没有复杂效果的控件有效,对于 EditText 以及 CompoundButton 这种自带特效的控件,不一定适用。还有一点,对于 AppCompatEditText,不仅只有 ACTION_DOWN 会触发其 OnTouchListener#onTouch(),ACTION_UP 也会触发。

SwitchCompat

单步调试发现,SwitchCompat 也消费了 DOWN 和 UP 事件,关键代码在 View 的 11181 行:

if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {            switch (action) {                case MotionEvent.ACTION_UP:// more code

此时,只有 (viewFlags & CLICKABLE) == CLICKABLE 成立,只要进入该 if 语句,就会返回 true。

既然消费了 DOWN 和 UP 事件,父 view 就不再消费了,这与实际结果相符。

总结

AppCompatEditText 和 SwitchCompat 这两个控件,有一个共同点,就是都是继承自 TextView。

这两个控件自身代码比较简单,都没有对 dispatchTouchEvent() 和 onTouchEvent() 方法进行重写,事件分发相关的逻辑都集中在 TextView 中。

TextView 对事件分发逻辑的处理相当复杂,具体问题当具体分析,不能用简单的通过是否显式绑定事件来做推断。

单步调试过程中绘出的视图树:
这里写图片描述

原创粉丝点击