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

深入解析Andoird应用开发中View的事件传递

程序员文章站 2024-03-09 13:05:47
下面以点击某个view之后的事件传递为例子。 首先分析view里的dispatchtouchevent()方法,它是点击view执行的第一个方法。 publ...

深入解析Andoird应用开发中View的事件传递

下面以点击某个view之后的事件传递为例子。
首先分析view里的dispatchtouchevent()方法,它是点击view执行的第一个方法。

public boolean dispatchtouchevent(motionevent event) {
 if (montouchlistener != null && (mviewflags & enabled_mask) == enabled &&
   montouchlistener.ontouch(this, event)) {
   return true;
 }
 return ontouchevent(event);
}

注意:里面包含两个回调函数 ontouch(),ontouchevent();如果控件绑定了ontouchlistener,且该控件是enabled,那么就执行ontouch()方法,如果该方法返回true,则说明该触摸事件已经被ontouchlistener监听器消费掉了,不会再往下分发了;但是如果返回false,则说明未被消费,继续往下分发到该控件的ontouchevent()去处理。

然后分析ontouchevent()方法,进行进一步的触摸事件处理。

if (((viewflags & clickable) == clickable || 
   (viewflags & long_clickable) == long_clickable)) { 
  switch (event.getaction()) { 
   case motionevent.action_up:
    ..... 
      performclick(); //响应点击事件 
    break; 
   case motionevent.action_down: 
   ..... break; 
   case motionevent.action_cancel: 
   ..... break; 
   case motionevent.action_move: 
   ..... break; 
  } 
  return true; 
} 
return false;

如果该控件是clickable 、long_clickable的,那么就可以响应对应事件,响应完后返回true继续响应。比如点击事件,先响应action_down,然后break并返回true,然后手抬起,又从dispatchtouchevent()分发下来,再响应action_up,里面会去performclick()响应点击事件。

响应点击事件

public boolean performclick() {
  sendaccessibilityevent(accessibilityevent.type_view_clicked);
  if (monclicklistener != null) {
     playsoundeffect(soundeffectconstants.click);
     monclicklistener.onclick(this);
     return true;
  }
  return false;
}

里面执行monclicklistener.onclick(this);即回调绑定监听器的onclick()函数。

关键点:
ontouch和ontouchevent的区别,又该如何使用?
答:
当view控件接受到触摸事件,如果控件绑定了ontouchlistener监听器,而且该控件是enable,那么就去执行ontouch()方法,如果返回true,则已经把触摸事件消费掉,不再向下传递;如果返回false,那么继续调用ontouchevent()事件。

android的touch事件传递到activity顶层的decorview(一个framelayout)之后,会通过viewgroup一层层往视图树的上面传递,最终将事件传递给实际接收的view。下面给出一些重要的方法。

dispatchtouchevent
事件传递到一个viewgroup上面时,会调用dispatchtouchevent。代码有删减

public boolean dispatchtouchevent(motionevent ev) {

  boolean handled = false;
  if (onfiltertoucheventforsecurity(ev)) {
    final int action = ev.getaction();
    final int actionmasked = action & motionevent.action_mask;

    // attention 1 :在按下时候清除一些状态
    if (actionmasked == motionevent.action_down) {
      cancelandcleartouchtargets(ev);
      //注意这个方法
      resettouchstate();
    }

    // attention 2:检查是否需要拦截
    final boolean intercepted;
    //如果刚刚按下 或者 已经有子view来处理
    if (actionmasked == motionevent.action_down
        || mfirsttouchtarget != null) {
      final boolean disallowintercept = (mgroupflags & flag_disallow_intercept) != 0;
      if (!disallowintercept) {
        intercepted = onintercepttouchevent(ev);
        ev.setaction(action); // restore action in case it was changed
      } else {
        intercepted = false;
      }
    } else {
      // 不是一个动作序列的开始 同时也没有子view来处理,直接拦截
      intercepted = true;
    }

     //事件没有取消 同时没有被当前viewgroup拦截,去找是否有子view接盘
    if (!canceled && !intercepted) {
        //如果这是一系列动作的开始 或者有一个新的pointer按下 我们需要去找能够处理这个pointer的子view
      if (actionmasked == motionevent.action_down
          || (split && actionmasked == motionevent.action_pointer_down)
          || actionmasked == motionevent.action_hover_move) {
        final int actionindex = ev.getactionindex(); // always 0 for down

        //上面说的触碰点32的限制就是这里导致
        final int idbitstoassign = split ? 1 << ev.getpointerid(actionindex)
            : touchtarget.all_pointer_ids;

        final int childrencount = mchildrencount;
        if (newtouchtarget == null && childrencount != 0) {
          final float x = ev.getx(actionindex);
          final float y = ev.gety(actionindex);

          //对当前viewgroup的所有子view进行排序,在上层的放在开始
          final arraylist<view> preorderedlist = buildorderedchildlist();
          final boolean customorder = preorderedlist == null
              && ischildrendrawingorderenabled();
          final view[] children = mchildren;
          for (int i = childrencount - 1; i >= 0; i--) {
            final int childindex = customorder
                ? getchilddrawingorder(childrencount, i) : i;
            final view child = (preorderedlist == null)
                ? children[childindex] : preorderedlist.get(childindex);

               // canviewreceivepointerevents visible的view都可以接受事件
               // istransformedtouchpointinview 计算是否落在点击区域上
            if (!canviewreceivepointerevents(child)
                || !istransformedtouchpointinview(x, y, child, null)) {
              ev.settargetaccessibilityfocus(false);
              continue;
            }

               //能够处理这个pointer的view是否已经处理之前的pointer,那么把
            newtouchtarget = gettouchtarget(child);
            if (newtouchtarget != null) {
              // child is already receiving touch within its bounds.
              // give it the new pointer in addition to the ones it is handling.
              newtouchtarget.pointeridbits |= idbitstoassign;
              break;
            }              }
            //attention 3 : 直接发给子view
            if (dispatchtransformedtouchevent(ev, false, child, idbitstoassign)) {
              // child wants to receive touch within its bounds.
              mlasttouchdowntime = ev.getdowntime();
              if (preorderedlist != null) {
                // childindex points into presorted list, find original index
                for (int j = 0; j < childrencount; j++) {
                  if (children[childindex] == mchildren[j]) {
                    mlasttouchdownindex = j;
                    break;
                  }
                }
              } else {
                mlasttouchdownindex = childindex;
              }
              mlasttouchdownx = ev.getx();
              mlasttouchdowny = ev.gety();
              newtouchtarget = addtouchtarget(child, idbitstoassign);
              alreadydispatchedtonewtouchtarget = true;
              break;
            }

          }
        }

      }
    }

    // 前面已经找到了接收事件的子view,如果为null,表示没有子view来接手,当前viewgroup需要来处理
    if (mfirsttouchtarget == null) {
      // viewgroup处理
      handled = dispatchtransformedtouchevent(ev, canceled, null,
          touchtarget.all_pointer_ids);
    } else {

        if(alreadydispatchedtonewtouchtarget) {
                   //ignore some code
          if (dispatchtransformedtouchevent(ev, cancelchild,
              target.child, target.pointeridbits)) {
            handled = true;
         }
        }

    }
  return handled;
}

上面代码中的attention在后面部分将会涉及,重点注意。

这里需要指出一点的是,一系列动作中的不同pointer可以分配给不同的view去响应。viewgroup会维护一个pointerid和处理view的列表touchtarget,一个touchtarget代表一个可以处理pointer的子view,当然一个view可以处理多个pointer,比如两根手指都在某一个子view区域。touchtarget内部使用一个int来存储它能处理的pointerid,一个int32位,这也就是上层为啥最多只能允许同时最多32点触碰。

看一下attention 3 处的代码,我们经常说view的dispatchtouchevent如果返回false,那么它就不能系列动作后面的动作,这是为啥呢?因为attention 3处如果返回false,那么它不会被记录到touchtarget中,viewgroup认为你没有能力处理这个事件。

这里可以看到,viewgroup真正处理事件是在dispatchtransformedtouchevent里面,跟进去看看:

dispatchtransformedtouchevent
private boolean dispatchtransformedtouchevent(motionevent event, boolean cancel,
    view child, int desiredpointeridbits) {

   //没有子类处理,那么交给viewgroup处理
  if (child == null) {
    handled = super.dispatchtouchevent(transformedevent);
  } else {
    final float offsetx = mscrollx - child.mleft;
    final float offsety = mscrolly - child.mtop;
    transformedevent.offsetlocation(offsetx, offsety);
    if (! child.hasidentitymatrix()) {
      transformedevent.transform(child.getinversematrix());
    }

    handled = child.dispatchtouchevent(transformedevent);
  }
  return handled;
}

可以看到这里不管怎么样,都会调用view的dispatchtouchevent,这是真正处理这一次点击事件的地方。

dispatchtouchevent
  public boolean dispatchtouchevent(motionevent event) {
    if (onfiltertoucheventforsecurity(event)) {
    //先走view的ontouch事件,如果ontouch返回true
    listenerinfo li = mlistenerinfo;
    if (li != null && li.montouchlistener != null
        && (mviewflags & enabled_mask) == enabled
        && li.montouchlistener.ontouch(this, event)) {
      result = true;
    }

    if (!result && ontouchevent(event)) {
      result = true;
    }
  }
    return result;
  }

我们给view设置的ontouch事件处在一个较高的优先级,如果ontouch执行返回true,那么就不会去走view的ontouchevent,而我们一些点击事件都是在ontouchevent中处理的,这也是为什么ontouch中返回true,view的点击相关事件不会被处理。

小小总结一下这个流程
viewgroup在接受到上级传下来的事件时,如果是一系列touch事件的开始(action_down),viewgroup会先看看自己需不需要拦截这个事件(onintercepttouchevent,viewgroup的默认实现直接返回false表示不拦截),接着viewgroup遍历自己所有的view。找到当前点击的那个view,马上调用目标view的dispatchtouchevent。如果目标view的dispatchtouchevent返回false,那么认为目标view只是在那个位置而已,它并不想接受这个事件,只想安安静静的做一个view(我静静地看着你们装*)。此时,viewgroup还会去走一下自己dispatchtouchevent,done!