欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-02-23 21:56:40
touch事件分发中只有两个主角:viewgroup和view。activity的touch事件事实上是调用它内部的viewgroup的touch事件,可以直接当成view...

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事件。来个简单版的代码加深理解:
 

/**
   * 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方法真正执行的代码是这样的

/**
   * 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方法

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事件分发机制有所帮助。