Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

android事件分发机制分析

$
0
0
触摸事件相关方法:

ViewGroup
  • dispatchTouchEvent(MotionEvent)          用于分发touch事件
  • onInterceptTouchEvent(MotionEvent)   用于是否中断touch事件
  • onTouchEvent(MotionEvent)                       用于处理touch事件

View、Activity
  • dispatchTouchEvent(MotionEvent)  
  • onTouchEvent(MotionEvent)  
具体代码如下:

1、Utils.java  代码如下:
  1. public class Utils {
  2. /**
  3. * 获取触摸事件的Action名称
  4. * @param ev
  5. */
  6. public static String getActionName(MotionEvent ev) {
  7. String action;
  8. switch (ev.getAction()) {
  9. case MotionEvent.ACTION_CANCEL:
  10. action = "CANCEL";
  11. break;
  12. case MotionEvent.ACTION_DOWN:
  13. action = "DOWN";
  14. break;
  15. case MotionEvent.ACTION_MOVE:
  16. action = "MOVE";
  17. break;
  18. case MotionEvent.ACTION_UP:
  19. action = "UP";
  20. break;
  21. default:
  22. action = "UNKNOWN_ACTION";
  23. break;
  24. }
  25. if (action.length() < 5) {
  26. for (int i = action.length(); i < 5; i++) {
  27. action += " ";
  28. }
  29. }
  30. return action;
  31. }
  32. }

2、ViewGroupA.java 代码如下:
  1. public class ViewGroupA extends LinearLayout {
  2. public ViewGroupA(Context context, AttributeSet attrs) {
  3. super(context, attrs);
  4. }
  5. @Override
  6. public boolean dispatchTouchEvent(MotionEvent ev) {
  7. System.out.println(Utils.getActionName(ev) + ", ViewGroupA.dispatch");
  8. boolean result = super.dispatchTouchEvent(ev);
  9. System.out.println(Utils.getActionName(ev) + ", ViewGroupA.dispatch = " + result);
  10. return result;
  11. }
  12. @Override
  13. public boolean onInterceptTouchEvent(MotionEvent ev) {
  14. boolean result = false;
  15. System.out.println(Utils.getActionName(ev) + ", ViewGroupA.intercept = " + result);
  16. return result;
  17. }
  18. @Override
  19. public boolean onTouchEvent(MotionEvent event) {
  20. boolean result = false;
  21. System.out.println(Utils.getActionName(event) + ", ViewGroupA.touch = " + result);
  22. return result;
  23. }
  24. }

布局效果如下:




运行代码,在蓝色的View上进行按下、移动、抬起,输出的Log如下:

          ---------------------------------------------
DOWN , Activity.dispatch
DOWN , ViewGroupA.dispatch
DOWN , ViewGroupA.intercept = false
DOWN , ViewGroupB.dispatch
DOWN , ViewGroupB.intercept = false
DOWN , ViewC.dispatch
DOWN , ViewC.touch = false
DOWN , ViewC.dispatch = false
DOWN , ViewGroupB.touch = false
DOWN , ViewGroupB.dispatch = false
DOWN , ViewGroupA.touch = false
DOWN , ViewGroupA.dispatch = false
DOWN , Activity.touch = false
DOWN , Activity.dispatch = false
---------------------------------------------
MOVE , Activity.dispatch
MOVE , Activity.touch = false
MOVE , Activity.dispatch = false
---------------------------------------------
UP   , Activity.dispatch
UP   , Activity.touch = false
UP   , Activity.dispatch = false
---------------------------------------------

从上面的Log可分析出:
  • 触摸事件最先是由Activity获得,然后是ViewGroupA、ViewGroupB、ViewC
  • 如果Down事件没有人处理,则事件丢失,后续的事件(如Move、Up)不再传递,直接由Activity进行处理
  • 所有的事件分发都是调用super.dispatchTouchEvent(ev)完成的,所以如果不调用这句代码则事件中止传递。但是要中止事件传递一般不会这么做,一般是在onInterceptTouchEvent(MotionEvent) 方法中处理,如果该方法返回true则中止。既然dispatchTouchEvent(ev)方法可以中止事件传递,为什么还要设计一个onInterceptTouchEvent(MotionEvent) 方法呢? 因为子View可以请求父View不要拦截事件,如ListView是可以上下滑动的,当处于滑动状态时候就会请求禁止父View的拦截触摸事件方法,让ListView可以一直获取到touch事件进行滚动。假设这个时候父View又想响应触摸事件怎么办?可以写到dispatchTouchEvent方法中,因为事件是先传到这个方法,然后再传递给ListView的。
  • 既然Activity最先获得事件,则可在Activity的dispatchTouchEvent方法不调用super.dispatchTouchEvent(ev);代码,则事件就不会进行分发了,可在此方法中调用onTouchEvent(ev)让它去处理。
  • 应用:假如在Activity中要一定要响应一些触摸事件,又怕事件传递后被消费了,也是相同道理,直接在Activity的dispatchTouchEvent方法中处理即可)

修改ViewGroupA的onInterceptTouchEvent让其返回true,代表拦截事件。
运行,然后按下、移动、抬起,Log如下:

---------------------------------------------
DOWN , Activity.dispatch
DOWN , ViewGroupA.dispatch
DOWN , ViewGroupA.intercept = true
DOWN , ViewGroupA.touch = false
DOWN , ViewGroupA.dispatch = false
DOWN , Activity.touch = false
DOWN , Activity.dispatch = false
---------------------------------------------
MOVE , Activity.dispatch
MOVE , Activity.touch = false
MOVE , Activity.dispatch = false
---------------------------------------------
UP   , Activity.dispatch
UP   , Activity.touch = false
UP   , Activity.dispatch = false

从上面的Log可分析出:
  • ViewGroupA在Down事件的时候中断事件传递后直接把Touch事件交由自己的onTouchEvent方法去处理
  • ViewGroupA中断触摸事件后只是子View(ViewGroupB和ViewC)不再获得touch事件,而父View(Activity)可以。
  • 虽然ViewGroupA中断触摸事件传递,由于它的onTouchEvent方法也没有处理Down事件,所以事件也丢失了,后续事件(如Move、Up)不再传给ViewGroupA,由Activity中行处理。所以,如果你想要获取后续事件,在处理了Down事件后一定要记得返回true。这里说的后续事件是指一次整体的操作,一般是由一个Down和多个Move和一个Up组成。按下然后移动然后弹起,这样的操作称为一次整体的操作。
修改ViewGroupA的onInterceptTouchEvent让其返回false,代表不拦截事件
修改ViewGroupB的onInterceptTouchEvent让其返回true,代表拦截事件,并修改onTouchEvent方法如下:
  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. boolean onTouchEvent = super.onTouchEvent(event);;
  4. switch (event.getAction()) {
  5. case MotionEvent.ACTION_DOWN:
  6. onTouchEvent = true;
  7. break;
  8. case MotionEvent.ACTION_MOVE:
  9. onTouchEvent = false;
  10. break;
  11. case MotionEvent.ACTION_UP:
  12. onTouchEvent = false;
  13. break;
  14. default:
  15. break;
  16. }
  17. System.out.println(MotionEventUtil.getMotionEventActionName(event) + ", ViewGroupB.touch = " + onTouchEvent);
  18. return onTouchEvent;
  19. }
运行,然后按下、移动、抬起,Log如下:

        ---------------------------------------------
    DOWN , Activity.dispatch
        DOWN , ViewGroupA.dispatch
        DOWN , ViewGroupA.intercept = false
        DOWN , ViewGroupB.dispatch
        DOWN , ViewGroupB.intercept = true
        DOWN , ViewGroupB.touch = true
        DOWN , ViewGroupB.dispatch = true
        DOWN , ViewGroupA.dispatch = true
        DOWN , Activity.dispatch = true
        ---------------------------------------------
        MOVE , Activity.dispatch
        MOVE , ViewGroupA.dispatch
        MOVE , ViewGroupA.intercept = false
        MOVE , ViewGroupB.dispatch
        MOVE , ViewGroupB.touch = false
        MOVE , ViewGroupB.dispatch = false
        MOVE , ViewGroupA.dispatch = false
        MOVE , Activity.touch = false
        MOVE , Activity.dispatch = false
        ---------------------------------------------
        MOVE , Activity.dispatch
        MOVE , ViewGroupA.dispatch
        MOVE , ViewGroupA.intercept = false
        MOVE , ViewGroupB.dispatch
        MOVE , ViewGroupB.touch = false
        MOVE , ViewGroupB.dispatch = false
        MOVE , ViewGroupA.dispatch = false
        MOVE , Activity.touch = false
        MOVE , Activity.dispatch = false
        ---------------------------------------------
        UP   , Activity.dispatch
        UP   , ViewGroupA.dispatch
        UP   , ViewGroupA.intercept = false
        UP   , ViewGroupB.dispatch
        UP   , ViewGroupB.touch = false
        UP   , ViewGroupB.dispatch = false
        UP   , ViewGroupA.dispatch = false
        UP   , Activity.touch = false
        UP   , Activity.dispatch = false


从上面的Log可分析出:
  • ViewGroupB中断了触摸事件的传递,并把事件交给自己的onTouchEvent方法处理。它的孩子ViewC就接收不到touch事件了
  • ViewGroupB中消费了Down事件(返回true),所以可以接收后续的Move、Up事件,dispatch方法接收到Move事件后并没有去调用onInterceptTouchEvent方法了,因为在接收到Down事件时候已经成为了目标View,非目标View不能接收后续事件,所以后续事件(Move、Up)就不需要再调用了onInterceptTouchEvent,因为不需要再传递了,所以直接把事件交给自己的onTouchEvent方法进行处理。
  • 虽然ViewGroupB在处理Move事件时返回了false,但是父View(ViewGroupA)的onTouchEvent方法并没有执行,这说明,谁消费了Down事件(返回true)谁就能接收后续事件(Move、Up),没有消费Down事件的其它View则接收不到(Activity例外)。
  • 虽然ViewGroupB在处理Move事件时返回了false,但是还是依旧可以接收后续事件的(Move、Up),那在处理Move和UP时返回false或返回true有什么区别吗?答:返回true则消费此事件,Activity的onTouchEvent方法就不会接收到了。
通过这个Demo可以模拟任何的不同情况,如处理Down事件返回true,Move事件返回false,事件拦截,请求禁止拦截等等。

总结:
  • touch事件传递其实就是一连串的方法调用,由Activity的dispatchTouchEvent方法开始调用,当这个方法调用结束时,这个touch事件就结束了。
  • 一个整体的touch事件由1个Donw和0 ~ n个Move和1个Up事件组成
  • 消费了Down事件的View称为目标View,目标View可接收后续事件(非目标View不接收后续事件)
  • Down事件如果没有任何View消费,则后续事件不再传递,直接由Activity的onTouchEvent方法处理,所以,想要处理其它事件首先要消费Down事件,也就是在接收到Down事件的时候返回true。
  • 拦截:
    • 如果在Down事件拦截,则把当前事件交自己的onTouchEvnet方法处理,如果此方法不消费Down事件,则事件丢失,后续事件由Activity的onTouchEvent方法处理
    • 如果在其它事件拦截,则不处理当前事件,且传一个Cancel事件给目标View,后续的事件就会交给自己的onTouchEvent方法处理(此时拦截了事件的View变成了目标View,虽然它没有消费Down事件)
    • 如果在Down事件不拦截,在事件分发返回后,如果Down事件没被消费,则会把事件交给自己的onTouchEvent方法处理。
    • 如果在其它事件不拦截(能接收其它事件说明有目标View),在事件分发返回后,不会把事件交给自己的onTouchEvent,即使目标View在处理(Move、Up)事件时返回false。
  • Activity不管有无目标View,也不管是Down还是Move、Up事件,只要分发调用返回时,如果事件没被消费,则交给自己的onTouchEvent方法处理
  • 可以调用getParent().requestDisallowInterceptTouchEvent(true)方法请求父View禁止拦截事件,这个方法会递归的请求所有的父View禁止拦截事件。
    • 注:如果想要获取到一个整体的touch事件,一定要消费Down事件,如果在Down事件的时候只是请求父View禁止拦截并不消费Down事件,虽然父View不再拦截了,但后续事件也接收不到了,哪个父View消费了Down事件哪个父View就可以接收到后续事件。
  • 有两个View并没有包含关系,但是有重叠,则上面的View先拿到事件,如果消费了,则事件不会传给另一个View。
  • 容器类一般都是调用ViewGroup的dispatchTouchEvent方法进行事件分发,其它类一般是调用View类的dispatchTouchEvent方法进行事件分发;通常默认的onInterceptTouchEvent、onTouchEvent方法都是返回false。
  • 应用技巧:
    • 父类的onTouchEvent方法要想执行,要么是等所有的子View都不消费Down事件,要么是父View把事件拦截。
    • 如果子类消费了Down事件,而父View又不想拦截但是又想处理这个事件,则父View可以在onInterceptTouchEvent或dispatchTouchEvent方法处理touch事件
    • 如果子View请求了禁止父View拦截,且父View还想要拦截的话,可在父View的dispatchTouchEvent方法中不调用super.dispatchTouchEvent则把事件拦截了。
作者:luomoBM 发表于2016/10/8 9:42:52 原文链接
阅读:16 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>