Android ViewGroup事件分发机制源码解读

175 阅读4分钟

分析了关于View的事件分发后,链接是Android View事件分发,分析ViewGroup事件分发 自定义一个ViewGroup

public class CustomLinearLayout extends LinearLayout {

    private static final String TAG = "CustomLinearLayout";

    public CustomLinearLayout(Context context,@Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG,"dispatchTouchEvent=ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG,"dispatchTouchEvent=ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG,"dispatchTouchEvent=ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG,"onInterceptTouchEvent=ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG,"onInterceptTouchEvent=ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG,"onInterceptTouchEvent=ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG,"onTouchEvent=ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG,"onTouchEvent=ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG,"onTouchEvent=ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }
}

布局文件如下:

    <com.xiaoma.restudy.customviews.CustomLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.xiaoma.restudy.customviews.CustomView
            android:id="@+id/mbt_cmb"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Custom View" />
    </com.xiaoma.restudy.customviews.CustomLinearLayout>

点击按钮后出发的日志如下:

E/CustomLinearLayout: dispatchTouchEvent=ACTION_DOWN
E/CustomLinearLayout: onInterceptTouchEvent=ACTION_DOWN
E/CustomView: dispatchTouchEvent=ACTION_DOWN
E/.customviews.CustomActivity: onTouch=ACTION_DOWN
E/CustomView: onTouchEvent=ACTION_DOWN
 E/CustomLinearLayout: dispatchTouchEvent=ACTION_MOVE
E/CustomLinearLayout: onInterceptTouchEvent=ACTION_MOVE
E/CustomView: dispatchTouchEvent=ACTION_MOVE
E/.customviews.CustomActivity: onTouch=ACTION_MOVE
E/CustomView: onTouchEvent=ACTION_MOVE
E/CustomLinearLayout: dispatchTouchEvent=ACTION_UP
E/CustomLinearLayout: onInterceptTouchEvent=ACTION_UP
E/CustomView: dispatchTouchEvent=ACTION_UP
E/.customviews.CustomActivity: onTouch=ACTION_UP
E/CustomView: onTouchEvent=ACTION_UP
E/.customviews.CustomActivity: onClick

#####总结:根据日志得出:事件出发的流程是CustomLinearLayout的dispatchTouchEvent-->CustomLinearLayout的onInterceptTouchEvent-->CustomView的dispatchTouchEvent-->CustomView的onTouchEvent 所以事件优先传递到ViewGroup再到View上 按照事件的分发顺利,查看并分析源码 1、ViewGroup的 dispatchTouchEvent

  • 检查安全策略,否则返回flase;
 /**
     * Filter the touch event to apply security policies.
     *
     * @param event The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should be dropped.
     *
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

#####如果事件被分发返回true,如果被丢弃返回false

  • intercepted 是否拦截由如下情况:1.如果是首次进入即事件等于ACTION_DOWN,否则是mFirstTouchTarget!=null的情况,需要根据disallowIntercept判断,如果disallowIntercept==true,则拦截等于false,否则拦截根据onInterceptTouchEvent方法返回;其他情况就是子View不处理,直接返回true; ###如果intercepted返回true,则拦截了子View的MOVE和UP
 if (!canceled && !intercepted) {

#####disallowIntercept 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean)进行设置

  final boolean intercepted;
            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 {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
  • 如果不是取消且没有拦截则开始寻找子View处理
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        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;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        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);
        }

        // Perform any necessary transformations and dispatch.
        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);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    } 
  • 如果child没有则直接交由父类super.dispatchTouchEvent(transformedEvent)处理,否则交给子View处理child.dispatchTouchEvent(transformedEvent);如果子View处理了,则交给子View处理且alreadyDispatchedToNewTouchTarget=true;
      // 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);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
  • 如果没有找到子View则handled交给super.dispatchTouchEvent(event)处理
  • 如果分发给子View,轮询所有子View则判断如果alreadyDispatchedToNewTouchTarget是true且target == newTouchTarget,表示已经有子View处理,直接返回true,否则交给父类或者子View

2、ViewGroup的onInterceptTouchEvent

 public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;

#####只要不是ACTION_DOWN都返回false,所以默认不拦截

  • 如果拦截的话,需要在ViewGroup集成类重写该方法,根据需要返回true;
@Override
	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;
	}

如果需要父类不处理事件,则需要在子类的dispatchTouchEvent内处理,如果是ViewGroup则 requestDisallowInterceptTouchEvent(true);如果是View则是 getParent().requestDisallowInterceptTouchEvent(true);如果不需要则设置为 requestDisallowInterceptTouchEvent(false);

  @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        getParent().requestDisallowInterceptTouchEvent(true);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG,"dispatchTouchEvent=ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG,"dispatchTouchEvent=ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG,"dispatchTouchEvent=ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(event);
    }