1.通过日志来分析事件传递相关方法的执行流程
为了更好的了解view的事件传递,我们可以自定义一个button继承自Button,并重写事件传递相关方法通过打日志来分析各个方法的执行顺序。
- public class MyButton extends Button {
- public MyButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.d("dispatchTouchEvent", "ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d("dispatchTouchEvent", "ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d("dispatchTouchEvent", "ACTION_UP");
- break;
- }
- return super.dispatchTouchEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.d("onTouchEvent", "ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d("onTouchEvent", "ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d("onTouchEvent", "ACTION_UP");
- break;
- }
- return super.onTouchEvent(event);
- }
- }
将自定义button添加到布局中
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/activity_main"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.tfx.test.MainActivity">
-
- <com.tfx.test.MyButton
- android:id="@+id/bt"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="button"/>
- </LinearLayout>
MainActivity代码,给自定义button注册setOnTouchEvent监听
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Button button = (Button) findViewById(R.id.bt);
- button.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent motionEvent) {
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.d("onTouch", "ACTION_DOWN");
- break;
-
- case MotionEvent.ACTION_MOVE:
- Log.d("onTouch", "ACTION_MOVE");
- break;
-
- case MotionEvent.ACTION_UP:
- Log.d("onTouch", "ACTION_UP");
- break;
- }
- return false;
- }
- });
- }
- }
重写了三个事件分发相关方法后,运行demo并单击button按钮,然后看输出的日志
- 12-19 09:22:56.478 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_DOWN
- 12-19 09:22:56.478 1837-1837/com.tfx.test D/onTouch: ACTION_DOWN
- 12-19 09:22:56.478 1837-1837/com.tfx.test D/onTouchEvent: ACTION_DOWN
- 12-19 09:22:56.556 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_UP
- 12-19 09:22:56.556 1837-1837/com.tfx.test D/onTouch: ACTION_UP
- 12-19 09:22:56.556 1837-1837/com.tfx.test D/onTouchEvent: ACTION_UP
如果点击按钮时手指稍微蹭一下,会执行多次move,如下
- 12-19 09:20:17.253 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_DOWN
- 12-19 09:20:17.253 1837-1837/com.tfx.test D/onTouch: ACTION_DOWN
- 12-19 09:20:17.253 1837-1837/com.tfx.test D/onTouchEvent: ACTION_DOWN
- 12-19 09:20:17.295 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_MOVE
- 12-19 09:20:17.296 1837-1837/com.tfx.test D/onTouch: ACTION_MOVE
- 12-19 09:20:17.296 1837-1837/com.tfx.test D/onTouchEvent: ACTION_MOVE
- 12-19 09:20:17.463 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_MOVE
- 12-19 09:20:17.463 1837-1837/com.tfx.test D/onTouch: ACTION_MOVE
- 12-19 09:20:17.463 1837-1837/com.tfx.test D/onTouchEvent: ACTION_MOVE
- 12-19 09:20:17.580 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_MOVE
- 12-19 09:20:17.580 1837-1837/com.tfx.test D/onTouch: ACTION_MOVE
- 12-19 09:20:17.580 1837-1837/com.tfx.test D/onTouchEvent: ACTION_MOVE
- 12-19 09:20:17.716 1837-1837/com.tfx.test D/dispatchTouchEvent: ACTION_UP
- 12-19 09:20:17.717 1837-1837/com.tfx.test D/onTouch: ACTION_UP
- 12-19 09:20:17.717 1837-1837/com.tfx.test D/onTouchEvent: ACTION_UP
从日志我们大致可以看出,不管是down、move、up执行的流程都是dispatchTouchEvent --> onTouch --> onTouchEvent。
onTouchListener的onTouch方法是有返回值的,如果把返回值改为return true再运行,会发现onTouchEvent方法得不到执行,这是为什么呢?此时若再给按钮注册单击事件,会发现单击事件也不会被触发,这又是为什么呢? 带着这两个问题,接下来我们对dispatchTouchEvent方法进行分析。
2.dispatchTouchEvent方法
进入button的父类TextView我们并没有发现该方法,继续在它的父类view里面找,此时就能看到dispatchTouchEvent方法的源码,如下:
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
- }
-
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
- }
- return onTouchEvent(event);
- }
主要就看第二个if语句,这里会有三个条件:
条件一:对mOnTouchListener进行非空判断,也就是判断是否注册了onTouchListener监听
条件二:判断view是否是enable,一般控件默认都是enable
条件三:判断onTouchEvent的onTouch方法是否返回true
如果这三个条件都成立,那么dispatchTouchEvent方法就返回true,否则执行onTouchEvent并返回。
接着说上面的第一个问题,当三个条件都满足了,dispatchTouchEvent方法就会返回ture,onTouchEvent方法将得不到执行,也就是说onTouchListener的onTouch方法的返回值决定了onTouchEvent方法能否得到执行,如果返回true事件将会被消费,不再继续传递给onTouchEvent方法,默认返回false,onTouchEvent方法将得到执行,到这里第一个问题就解决了。
接着说问题二,根据问题一,我们可以断定单击事件的onClick方法是在onTouchEvent方法中执行的,只有onTouchEvent方法得到执行,才能响应单击事件。还有onTouch方法的执行优先于onClick方法,onTouch方法做的比onClick更多,能响应dowm、move、up多个手势。
3.onTouchEvent方法