美文网首页
Android ViewGroup事件分发机制源码解读

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

作者: Android_小马范儿 | 来源:发表于2019-03-21 15:53 被阅读0次

    分析了关于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);
        }
    

    相关文章

      网友评论

          本文标题:Android ViewGroup事件分发机制源码解读

          本文链接:https://www.haomeiwen.com/subject/fzpxvqtx.html