30分钟搞清楚Android Touch事件分发机制

时间:2022-02-22 07:08:58

touch事件分发中只有两个主角:viewgroup和view。activity的touch事件事实上是调用它内部的viewgroup的touch事件,可以直接当成viewgroup处理。

view在viewgroup内,viewgroup也可以在其他viewgroup内,这时候把内部的viewgroup当成view来分析。

viewgroup的相关事件有三个:onintercepttouchevent、dispatchtouchevent、ontouchevent。view的相关事件只有两个:dispatchtouchevent、ontouchevent。

先分析viewgroup的处理流程:首先得有个结构模型概念:viewgroup和view组成了一棵树形结构,最顶层为activity的viewgroup,下面有若干的viewgroup节点,每个节点之下又有若干的viewgroup节点或者view节点,依次类推。如图:

30分钟搞清楚Android Touch事件分发机制

当一个touch事件(触摸事件为例)到达根节点,即acitivty的viewgroup时,它会依次下发,下发的过程是调用子view(viewgroup)的dispatchtouchevent方法实现的。简单来说,就是viewgroup遍历它包含着的子view,调用每个view的dispatchtouchevent方法,而当子view为viewgroup时,又会通过调用viwgroup的dispatchtouchevent方法继续调用其内部的view的dispatchtouchevent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchtouchevent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchtouchevent返回结果为true,那么⑥-⑦-③-④将都接收不到本次touch事件。来个简单版的代码加深理解:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
   * viewgroup
   * @param ev
   * @return
   */
  public boolean dispatchtouchevent(motionevent ev){
    ....//其他处理,在此不管
    view[] views=getchildview();
    for(int i=0;i<views.length;i++){
      //判断下touch到屏幕上的点在该子view上面
      if(...){
      if(views[i].dispatchtouchevent(ev))
       return true;
       }
    }
    ...//其他处理,在此不管
  }
  /**
   * view
   * @param ev
   * @return
   */
  public boolean dispatchtouchevent(motionevent ev){
    ....//其他处理,在此不管
    return false;
  }

在此可以看出,viewgroup的dispatchtouchevent是真正在执行“分发”工作,而view的dispatchtouchevent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是ontouchevent事件,事实上子view的dispatchtouchevent方法真正执行的代码是这样的

?
1
2
3
4
5
6
7
8
9
/**
   * view
   * @param ev
   * @return
   */
  public boolean dispatchtouchevent(motionevent ev){
    ....//其他处理,在此不管
    return ontouchevent(event);
  }

一般情况下,我们不该在普通view内重写dispatchtouchevent方法,因为它并不执行分发逻辑。当touch事件到达view时,我们该做的就是是否在ontouchevent事件中处理它。

那么,viewgroup的ontouchevent事件是什么时候处理的呢?当viewgroup所有的子view都返回false时,ontouchevent事件便会执行。由于viewgroup是继承于view的,它其实也是通过调用view的dispatchtouchevent方法来执行ontouchevent事件。

 

在目前的情况看来,似乎只要我们把所有的ontouchevent都返回false,就能保证所有的子控件都响应本次touch事件了。但必须要说明的是,这里的touch事件,只限于acition_down事件,即触摸按下事件,而aciton_up和action_move却不会执行。事实上,一次完整的touch事件,应该是由一个down、一个up和若干个move组成的。down方式通过dispatchtouchevent分发,分发的目的是为了找到真正需要处理完整touch请求的view。当某个view或者viewgroup的ontouchevent事件返回true时,便表示它是真正要处理这次请求的view,之后的aciton_up和action_move将由它处理。当所有子view的ontouchevent都返回false时,这次的touch请求就由根viewgroup,即activity自己处理了。

看看改进后的viewgroup的dispatchtouchevent方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
view mtarget=null;//保存捕获touch事件处理的view
  public boolean dispatchtouchevent(motionevent ev) {
 
    //....其他处理,在此不管
    
    if(ev.getaction()==keyevent.action_down){
      //每次down事件,都置为null
 
      if(!onintercepttouchevent()){
      mtarget=null;
      view[] views=getchildview();
      for(int i=0;i<views.length;i++){
        if(views[i].dispatchtouchevent(ev))
          mtarget=views[i];
          return true;
      }
     }
    }
    //当子view没有捕获down事件时,viewgroup自身处理。这里处理的touch事件包含down、up和move
    if(mtarget==null){
      return super.dispatchtouchevent(ev);
    }
    //...其他处理,在此不管
    if(onintercepttouchevent()){
     //...其他处理,在此不管 
     }
//这一步在action_down中是不会执行到的,只有move和up才会执行到。
    return mtarget.dispatchtouchevent(ev);
 
  }

viewgroup还有个onintercepttouchevent,看名字便知道这是个拦截事件。这个拦截事件需要分两种情况来说明:

1.假如我们在某个viewgroup的onintercepttouchevent中,将action为down的touch事件返回true,那便表示将该viewgroup的所有下发操作拦截掉,这种情况下,mtarget会一直为null,因为mtarget是在down事件中赋值的。由于mtarge为null,该viewgroup的ontouchevent事件被执行。这种情况下可以把这个viewgroup直接当成view来对待。

2.假如我们在某个viewgroup的onintercepttouchevent中,将acion为down的touch事件都返回false,其他的都返回true,这种情况下,down事件能正常分发,若子view都返回false,那mtarget还是为空,无影响。若某个子view返回了true,mtarget被赋值了,在action_move和aciton_up分发到该viewgroup时,便会给mtarget分发一个action_delete的motionevent,同时清空mtarget的值,使得接下去的action_move(如果上一个操作不是up)将由viewgroup的ontouchevent处理。

情况一用到的比较多,情况二个人还未找到使用场景。

从头到尾总结一下:

1.touch事件分发中只有两个主角:viewgroup和view。viewgroup包含onintercepttouchevent、dispatchtouchevent、ontouchevent三个相关事件。view包含dispatchtouchevent、ontouchevent两个相关事件。其中viewgroup又继承于view。

2.viewgroup和view组成了一个树状结构,根节点为activity内部包含的一个viwgroup。

3.触摸事件由action_down、action_move、aciton_up组成,其中一次完整的触摸事件中,down和up都只有一个,move有若干个,可以为0个。

4.当acitivty接收到touch事件时,将遍历子view进行down事件的分发。viewgroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的view,这个view会在ontouchuevent结果返回true。

5.当某个子view返回true时,会中止down事件的分发,同时在viewgroup中记录该子view。接下去的move和up事件将由该子view直接进行处理。由于子view是保存在viewgroup中的,多层viewgroup的节点结构时,上级viewgroup保存的会是真实处理事件的view所在的viewgroup对象:如viewgroup0-viewgroup1-textview的结构中,textview返回了true,它将被保存在viewgroup1中,而viewgroup1也会返回true,被保存在viewgroup0中。当move和up事件来时,会先从viewgroup0传递至viewgroup1,再由viewgroup1传递至textview。

6.当viewgroup中所有子view都不捕获down事件时,将触发viewgroup自身的ontouch事件。触发的方式是调用super.dispatchtouchevent函数,即父类view的dispatchtouchevent方法。在所有子view都不处理的情况下,触发acitivity的ontouchevent方法。

7.onintercepttouchevent有两个作用:1.拦截down事件的分发。2.中止up和move事件向目标view传递,使得目标view所在的viewgroup捕获up和move事件。
 补充:

“触摸事件由action_down、action_move、aciton_up组成,其中一次完整的触摸事件中,down和up都只有一个,move有若干个,可以为0个。”,这里补充下其实up事件是可能为0个的。
以上就是本文的全部内容,希望对大家理解touch事件分发机制有所帮助。