Android ViewGroup的事件分发机制-源码分析

为了更好的理解ViewGroup的事件分发机制,我们在自定义一个MyLinerLayout.

public class MyLinearLayout extends LinearLayout
{
    private static final String TAG = MyLinearLayout.class.getSimpleName();
 
    public MyLinearLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
 
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        switch (action)
        {
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "dispatchTouchEvent ACTION_UP");
            break;
 
        default:
            break;
        }
        return super.dispatchTouchEvent(ev);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
 
        int action = event.getAction();
 
        switch (action)
        {
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "onTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e(TAG, "onTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "onTouchEvent ACTION_UP");
            break;
 
        default:
            break;
        }
 
        return super.onTouchEvent(event);
    }
 
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        
        int action = ev.getAction();
        switch (action)
        {
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
            break;
 
        default:
            break;
        }
        
        return super.onInterceptTouchEvent(ev);
    }
 
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)
    {
        Log.e(TAG, "requestDisallowInterceptTouchEvent ");
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
 
}
点击Button的时候可以看到打印信息:

 E/MyLinearLayout(959): dispatchTouchEvent ACTION_DOWN
 E/MyLinearLayout(959): onInterceptTouchEvent ACTION_DOWN
 E/MyButton(959): dispatchTouchEvent ACTION_DOWN
 E/MyButton(959): onTouchEvent ACTION_DOWN
 E/MyButton(959): onTouchEvent ACTION_MOVE
 E/MyLinearLayout(959): dispatchTouchEvent ACTION_MOVE
 E/MyLinearLayout(959): onInterceptTouchEvent ACTION_MOVE
 E/MyButton(959): dispatchTouchEvent ACTION_MOVE
 E/MyButton(959): onTouchEvent ACTION_MOVE
 E/MyLinearLayout(959): dispatchTouchEvent ACTION_UP
 E/MyLinearLayout(959): onInterceptTouchEvent ACTION_UP
 E/MyButton(959): dispatchTouchEvent ACTION_UP
 E/MyButton(959): onTouchEvent ACTION_UP

 
 

可以看到大体的事件流程为:

 
 

MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent

可以看出,在View上触发事件,最先捕获到事件的为View所在的ViewGroup,然后才会到View自身。

下面来看下ViewGroup的dispatchTouchEvent的源码:

代码较长,先看下down事件:

 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!onFilterTouchEventForSecurity(ev)) {
            return false;
        }
 
        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;
 
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 
        if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                //触发down的时候把mMotionTarget重置为null
                mMotionTarget = null;
            }
            // 如果不允许拦截或者允许拦截了但是没有拦截就会循环遍历里面的View
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
 
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {
                        child.getHitRect(frame);
                        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并且返回值==true,那么mMotionTarget就会被赋值成当前的子View
//并且返回true,down事件分发结束
if (child.dispatchTouchEvent(ev)) { // Event handled, we have a target now. mMotionTarget = child; return true; } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } }

move的源码:

    @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;

        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

        //获取down事件时的mMotionTarget
        final View target = mMotionTarget;

        //允许拦截并且拦截了
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
           //放在下面讲拦截的源码
        }

        final float xc = scrolledXFloat - (float) target.mLeft;

        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);
        //没有拦截就会调用子view的分发方法
        return target.dispatchTouchEvent(ev);
    }
up事件同样的道理,就不贴出了。

1、ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget,然后调用 mMotionTarget.dispatchTouchEvent

2、ACTION_MOVE中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

3、ACTION_UP中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

关于ViewGroup的拦截方法:

public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        switch (action)
        {
        case MotionEvent.ACTION_DOWN:
            //如果你觉得需要拦截
            return true ; 
        case MotionEvent.ACTION_MOVE:
            //如果你觉得需要拦截
            return true ; 
        case MotionEvent.ACTION_UP:
            //如果你觉得需要拦截
            return true ; 
        }
        
        return false;
    }

默认是不拦截的,即返回false;如果你需要拦截,只要return true就行了,这要该事件就不会往子View传递了,并且如果你在DOWN retrun true ,

则DOWN,MOVE,UP子View都不会捕获事件;如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件

原因很简单,当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget 置为null ;

如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件;

此时子View希望依然能够响应MOVE和UP时该咋办呢?

Android给我们提供了一个方法:requestDisallowInterceptTouchEvent(boolean) 用于设置是否允许拦截,我们在子View的dispatchTouchEvent中直接这么写

public boolean dispatchTouchEvent(MotionEvent event)
    {
        getParent().requestDisallowInterceptTouchEvent(true);  
        int action = event.getAction();
 
        switch (action)
        {
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "dispatchTouchEvent ACTION_UP");
            break;
 
        default:
            break;
        }
        return super.dispatchTouchEvent(event);
    }
getParent().requestDisallowInterceptTouchEvent(true);
这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。
看下ViewGroup的Move和Up的拦截源码:
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)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            }
            // 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;
        }
我们把disallowIntercept设置为true时,!disallowIntercept直接为false,于是拦截的方法体就被跳过了,也就失效了。

上面的流程都是正常的分发流程,如果没有找到合适的子View,或者子View的dispatchTouchEvent返回false怎么办?

这是上面down事件的部分代码

if (child.dispatchTouchEvent(ev)) {
  mMotionTarget = child;
  return true;
}
只有在child.dispatchTouchEvent(ev)返回true了,才会认为找到了能够处理当前事件的View,即mMotionTarget = child,否则仍旧是null

final View target = mMotionTarget;
        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);
        }
我们没有一个能够处理该事件的目标元素,意味着我们需要自己处理

总结:

1.如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发。

2.可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法

3.子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true);  阻止ViewGroup对其MOVE或者UP事件进行拦截;

猜你喜欢

转载自www.cnblogs.com/lianzhen/p/11276961.html