Android进阶系列之源码分析事件分发责任链模式

翱翔于源码的海洋,痛并快乐着....


什么是责任链模式?

责任链模式(Chain of Responsibility)的目标是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。在处理用户的请求时可能要根据不同的情况对请求添加不同的处理逻辑,在这时候就可以利用责任链进行设计。当需要添加一个处理逻辑时可以很方便的添加一个处理的节点。

首先要有一个抽象的Handler处理类。而链式的关键在于持有下一个Handler的引用。

public abstract class HandlerMsg {
    public static final int LEVEL_2 = 2;
    public static final int LEVEL_3 = 3;
    public static final int LEVEL_1 = 1;

    @IntDef(value = {LEVEL_2,LEVEL_1,LEVEL_3})
    @Retention(RetentionPolicy.SOURCE)
    public @interface level{}




    //下一个请求的引用
    @NonNull
    protected HandlerMsg nextHandler;
    //传入请求的等级
    public abstract HandlerMsg handleRequest(int level);
    //处理消息
    protected abstract HandlerMsg handleMessage();
    //链式建立
    @CallSuper
    public HandlerMsg setHandlerMsg(HandlerMsg nextHandler){
        this.nextHandler = nextHandler;
        return this;
    }

}
这里的抽象类有请求传入方法和请求处理方法,请求处理方法使用protected,让子类需实现该方法且只能内部调用。setHandlerMsg这个方法主要用于链式建立,子类可以直接调用该方法,形成链式请求。在看看子类如何实现。

public class HandlerLevel1 extends HandlerMsg {

    //请求等级
    private int requestLevel = LEVEL_1;

    @Override
    public HandlerMsg handleRequest(@level int level) {
        if (level == requestLevel){
            handleMessage();
        }else {
            nextHandler.handleRequest(level);
        }
        return this;
    }

    @Override
    protected HandlerMsg handleMessage() {
        Log.i("tag00","级别1事件,处理完成。");
        return this;
    }

}
public class HandlerLevel2 extends HandlerMsg {

    //请求等级
    private int requestLevel = LEVEL_2;

    @Override
    public HandlerMsg handleRequest(@level int level) {
        if (requestLevel == level){
            handleMessage();
        }else {
            nextHandler.handleRequest(level);
        }
        return this;
    }

    @Override
    protected HandlerMsg handleMessage() {
        Log.i("tag00","级别2事件,处理完成。");
        return this;
    }
}
public class HandlerLevel3 extends HandlerMsg {

    //请求等级
    private int requestLevel = LEVEL_3;

    @Override
    public HandlerMsg handleRequest(@level int level) {
        if (requestLevel == level){
            handleMessage();
        }else {
            nextHandler.handleRequest(level);
        }
        return this;
    }

    @Override
    protected HandlerMsg handleMessage() {
        Log.i("tag00","级别3事件,处理完成。");
        return this;
    }
}
三个子类,大同小异。设置自己的级别,当请求来了时,先对级别进行判定,若是请求的级别和自己的级别相同,那么就 处理该请求并拦截。

那么在MainActivity如何调用呢。

        //设置请求等级
        int requestLevel = HandlerMsg.LEVEL_3;
        //形成链式请求
        new HandlerLevel1().setHandlerMsg(new HandlerLevel2().setHandlerMsg(new HandlerLevel3())).handleRequest(requestLevel);
  new HandlerLevel1().setHandlerMsg(new HandlerLevel2().setHandlerMsg(new HandlerLevel3())).handleRequest(requestLevel);这句代码就形成了链式,handlerLevel1持有 handlerLevel2的引用,handlerLevel2持有handlerLevel3的引用。而传入请求的入口必须在链式的开端,也就是handlerLevel1的handleRequest方法传入,一次向下传递,这就是典型的责任链模式。

责任链模式介绍完了,就开始撸事件分发的源码了。来吧,首先我们要明白点击事件的流程,我们点击屏幕第一个接收到点击事件的是哪个控件呢?是Activity,PhoneWindow?还是DecorView呢? 答案是Activity,这是看源码知道的。有兴趣的猿友可以自己去深入研究,我这里把大概的源码流程讲述一遍。

Activity的dispatchTouchEvent()

/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
在Activity的源码dispatchTouchEvent中,调用了getWindow().superDispatchTouchEvent(ev)。也就是点击事件是先到Activity之后在到Window

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();
   
    ................
}
在Activity的mWindow是在attach()中创建。实现类是PhoneWindow。那么在看看PhoneWindow的superDispatchTouchEvent.

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
在superDispatchTouchEvent中又调用了mDecor的superDispatchTouchEvent。

private DecorView mDecor;
mDecor是DecorView的对象,那么到这里基本流程知道了。Activity调用PhoneWindow的superDispatchTouchEvent,而在PhoneWindow的superDispatchTouchEvent里调用DecorView的superDispatchTouchEvent。这就是大致的点击事件分发流程。


先以一个简单的例子,一个Activity里有个RelatieLayout嵌套TextView。一个完整的没有人为干扰的点击事件传递是dispatchTouchEvent(Activity)-->dispatchTouchEvent(RelatieLayout)->onInterceptTouchEvent(RelatieLayout)->dispatchTouchEvent(TextView)->onTouchListener(TextView)->onTouchEvent(TextView)->onTouchEvent(Activity)。这是Log打出的基本流程,那么结果我们知道了,至于为什么以及里面有多少种情况依然未知,那只有去看源码了。

Activity的dispatchTouchEvent(MotionEvent ev)

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
在点击了手机屏幕之后,先是从这里开始向下层传递事件,上面介绍了在getWindow().superDispatchTouchEvent(ev)最终是调用了DecorView的super.dispatchTouchEvent(),而DecorView继承于ViewGroup,所以最终调用的是ViewGroup的dispatchTouchEvent()方法。那么我们来看看ViewGroup的dispatchTouchEvent到底有什么逻辑。

@Override  
   public boolean dispatchTouchEvent(MotionEvent ev) {  
       final int action = ev.getAction();  
       final float xf = ev.getX();  
       final float yf = ev.getY();  
       final float scrolledXFloat = xf + mScrollX;  
       final float scrolledYFloat = yf + mScrollY;  
       final Rect frame = mTempRect;  
  
       //这个值默认是false, 然后我们可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法  
       //来改变disallowIntercept的值  
       boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  
       //这里是ACTION_DOWN的处理逻辑  
       if (action == MotionEvent.ACTION_DOWN) {  
        //清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null  
           if (mMotionTarget != null) {  
               mMotionTarget = null;  
           }  
  
           //disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法  
           if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
               ev.setAction(MotionEvent.ACTION_DOWN);  
               final int scrolledXInt = (int) scrolledXFloat;  
               final int scrolledYInt = (int) scrolledYFloat;  
               final View[] children = mChildren;  
               final int count = mChildrenCount;  
               //遍历其子View  
               for (int i = count - 1; i >= 0; i--) {  
                   final View child = children[i];  
                     
                   //如果该子View是VISIBLE或者该子View正在执行动画, 表示该View才  
                   //可以接受到Touch事件  
                   if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                           || child.getAnimation() != null) {  
                    //获取子View的位置范围  
                       child.getHitRect(frame);  
                         
                       //如Touch到屏幕上的点在该子View上面  
                       if (frame.contains(scrolledXInt, scrolledYInt)) {  
                           // offset the event to the view's coordinate system  
                           final float xc = scrolledXFloat - child.mLeft;  
                           final float yc = scrolledYFloat - child.mTop;  
                           ev.setLocation(xc, yc);  
                           child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                             
                           //调用该子View的dispatchTouchEvent()方法  
                           if (child.dispatchTouchEvent(ev))  {  
                               // 如果child.dispatchTouchEvent(ev)返回true表示  
                            //该事件被消费了,设置mMotionTarget为该子View  
                               mMotionTarget = child;  
                               //直接返回true  
                               return true;  
                           }  
                           // The event didn't get handled, try the next view.  
                           // Don't reset the event's location, it's not  
                           // necessary here.  
                       }  
                   }  
               }  
           }  
       }  
  
       //判断是否为ACTION_UP或者ACTION_CANCEL  
       boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
               (action == MotionEvent.ACTION_CANCEL);  
  
       if (isUpOrCancel) {  
           //如果是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false  
        //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true  
        //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false  
        //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false  
           mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
       }  
  
       // The event wasn't an ACTION_DOWN, dispatch it to our target if  
       // we have one.  
       final View target = mMotionTarget;  
       //mMotionTarget为null意味着没有找到消费Touch事件的View, 所以我们需要调用ViewGroup父类的  
       //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法  
       if (target == null) {  
           // We don't have a target, this means we're handling the  
           // event as a regular view.  
           ev.setLocation(xf, yf);  
           if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
               ev.setAction(MotionEvent.ACTION_CANCEL);  
               mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
           }  
           return super.dispatchTouchEvent(ev);  
       }  
  
       //这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE  
       //ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的  
       //Touch事件, 将ACTION_CANCEL派发给target,然后直接返回true  
       //表示消费了此Touch事件  
       if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
           final float xc = scrolledXFloat - (float) target.mLeft;  
           final float yc = scrolledYFloat - (float) target.mTop;  
           mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
           ev.setAction(MotionEvent.ACTION_CANCEL);  
           ev.setLocation(xc, yc);  
             
           if (!target.dispatchTouchEvent(ev)) {  
           }  
           // clear the target  
           mMotionTarget = null;  
           // Don't dispatch this event to our own view, because we already  
           // saw it when intercepting; we just want to give the following  
           // event to the normal onTouchEvent().  
           return true;  
       }  
  
       if (isUpOrCancel) {  
           mMotionTarget = null;  
       }  
  
       // finally offset the event to the target's coordinate system and  
       // dispatch the event.  
       final float xc = scrolledXFloat - (float) target.mLeft;  
       final float yc = scrolledYFloat - (float) target.mTop;  
       ev.setLocation(xc, yc);  
  
       if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
           ev.setAction(MotionEvent.ACTION_CANCEL);  
           target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
           mMotionTarget = null;  
       }  
  
       //如果没有拦截ACTION_MOVE, ACTION_DOWN的话,直接将Touch事件派发给target  
       return target.dispatchTouchEvent(ev);  
   }  
在ViewGroup的dispatchTouchEvent最关键的是对子类的事件分发。
// Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }
 
		//如果没有子类,那么就调用父类的dispatchTouchEvent
        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());
            }
			//如果有子类,那么就调用子类的dispatchTouchEvent。
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        transformedEvent.recycle();
        return handled;
    }

那么在这个例子中,我们可以使用HierarchyViewer工具查看该项目的ViewTree.参考:http://blog.csdn.net/sw5131899/article/details/53994445


上面的FrameLayout对应的是TitleView,而下面的FrameLayout对应的是ContentView。我们平时在Activity中调用的setContentView就是设置下面的FrameLayout的值。回到事件的传递,DecorView的唯一子类就是LinearLayout也是ViewGroup的子类,流程和上述一致,当到了LinearLayout的dispatchTransformedTouchEvent,里面就有两个子类,我们点击是下面的Framelayout,当事件来到RelativeLayout时,RelativeLayout会调用onInterceptTouchEvent,但是RelativeLayout没有重写onInterceptTouchEvent,所以调用ViewGourp的onInterceptTouchEvent,

  public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

默认返回的是false。表示不对事件进行拦截。如果有特殊需求,可以重写该方法返回true即拦截该事件。(返回true的情况这里不做介绍了,需要了解的猿友就自己去看源码吧)。事件不拦截之后,调用RelativeLayout的dispatchTransformedTouchEvent,子类就一个,TextView。好啦,事件终于通过重重分配来到了TextView,TextView继承于View,看看View的dispatchTouchEvent();

 public boolean dispatchTouchEvent(MotionEvent event) {
 
		......
		
        boolean result = false;

        
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            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;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  mOnTouchListener.onTouch(this, event)) ;

在这个方法里面,先进行了一个判断

第一个条件mOnTouchListener就是我们调用View的setTouchListener()方法设置的

第二个条件是判断View是否为enabled的, View一般都是enabled,除非你手动设置为disabled

第三个条件就是OnTouchListener接口的onTouch()方法的返回值了,如果调用了setTouchListener()设置OnTouchListener,并且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回true,否则就执行View的onTouchEvent() 并返回View的onTouchEvent()的值。这也是在调用onTouchEvent方法之前会调用onTouchListener的原因。

if (!result && onTouchEvent(event)) {
                result = true;
            }
在这里调用了onTouchEvent,如果 dispatchTouchEvent的result为ture,则不会调用onTouchEvent。在onTouchEvent中又对点击、长点击和MOVE、DOWN、UP等手势做了分别处理。

public boolean onTouchEvent(MotionEvent event) {  
      final int viewFlags = mViewFlags;  
  
      if ((viewFlags & ENABLED_MASK) == DISABLED) {  
          return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
      }  
  
      //如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null  
      if (mTouchDelegate != null) {  
          if (mTouchDelegate.onTouchEvent(event)) {  
              return true;  
          }  
      }  
  
      //如果View是clickable或者longClickable的onTouchEvent就返回true, 否则返回false  
      if (((viewFlags & CLICKABLE) == CLICKABLE ||  
              (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
          switch (event.getAction()) {  
              case MotionEvent.ACTION_UP:  
                  boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                  if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                      boolean focusTaken = false;  
                      if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                          focusTaken = requestFocus();  
                      }  
  
                      if (!mHasPerformedLongPress) {  
                          removeLongPressCallback();  
  
                          if (!focusTaken) {  
                              if (mPerformClick == null) {  
                                  mPerformClick = new PerformClick();  
                              }  
                              if (!post(mPerformClick)) {  
                                  performClick();  
                              }  
                          }  
                      }  
  
                      if (mUnsetPressedState == null) {  
                          mUnsetPressedState = new UnsetPressedState();  
                      }  
  
                      if (prepressed) {  
                          mPrivateFlags |= PRESSED;  
                          refreshDrawableState();  
                          postDelayed(mUnsetPressedState,  
                                  ViewConfiguration.getPressedStateDuration());  
                      } else if (!post(mUnsetPressedState)) {  
                          mUnsetPressedState.run();  
                      }  
                      removeTapCallback();  
                  }  
                  break;  
  
              case MotionEvent.ACTION_DOWN:  
                  if (mPendingCheckForTap == null) {  
                      mPendingCheckForTap = new CheckForTap();  
                  }  
                  mPrivateFlags |= PREPRESSED;  
                  mHasPerformedLongPress = false;  
                  postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                  break;  
  
              case MotionEvent.ACTION_CANCEL:  
                  mPrivateFlags &= ~PRESSED;  
                  refreshDrawableState();  
                  removeTapCallback();  
                  break;  
  
              case MotionEvent.ACTION_MOVE:  
                  final int x = (int) event.getX();  
                  final int y = (int) event.getY();  
  
                  //当手指在View上面滑动超过View的边界,  
                  int slop = mTouchSlop;  
                  if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                          (y < 0 - slop) || (y >= getHeight() + slop)) {  
                      // Outside button  
                      removeTapCallback();  
                      if ((mPrivateFlags & PRESSED) != 0) {  
                          removeLongPressCallback();  
  
                          mPrivateFlags &= ~PRESSED;  
                          refreshDrawableState();  
                      }  
                  }  
                  break;  
          }  
          return true;  
      }  
  
      return false;  
  }  
在ACTION_UP的处理中,调用了performClick()。这是调用该View的点击事件。也就是说如果在ACTION_UP事件分发中对事件进行拦截不在onTouchEvent中处理,就不会调用onClick()回调。到这里点击事件从Activity分发到了TextView,如果TextView没做任何处理。那么返回的是默认flase。那么最后会回到父类的onTouchEvent方法中进行处理。最后附上流程图(引用)。

源码真的是博大精深,任重道远,吾将上下而求索!下面来总结一下吧:(引用)

1.Activity的最顶层Window是PhoneWindow,PhoneWindow的最顶层View是DecorView

2.一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的

3.View的长按事件是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,并且不能产生ACTION_MOVE

4.View的点击事件是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false

5.如果View设置了onTouchListener了,并且onTouch()方法返回true,则不执行View的onTouchEvent()方法,也表示View消费了Touch事件,返回false则继续执行onTouchEvent()

6.Touch事件是从最顶层的View一直分发到手指touch的最里层的View,如果最里层View消费了ACTION_DOWN事件(设置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才会触发ACTION_MOVE,ACTION_UP的发生,如果某个ViewGroup拦截了Touch事件,则Touch事件交给ViewGroup处理

7.Touch事件的分发过程中,如果消费了ACTION_DOWN,而在分发ACTION_MOVE的时候,某个ViewGroup拦截了Touch事件,就像上面那个自定义CustomLayout,则会将ACTION_CANCEL分发给该ViewGroup下面的Touch到的View,然后将Touch事件交给ViewGroup处理,并返回true



参考:http://blog.csdn.net/xiaanming/article/details/21696315 -- 作者:xiaanming
  【动脑学院







  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值