Android 事件分发机制

1.简述

谈到自定义View,不可避免的要涉及View的各种事件,由于View并非独立存在其必要有父体和子体,而其触发响应事件,又不得不触及事件传递等。故而事件分发作为完善View触发事件以及解决各种冲突起着至关重要的作用。我这里由于有限水平,就不讲解原理了,举几个我遇到的小例子,概念我提供一些我觉得不错的博客,不对的地方多多指出,也希望能帮助一些开发者。

事件分发的小案例

目的:

通过重写包括相对布局,以及子ViewButton,打印其执行的Log,去观察其事件调用的顺序。窥探在Java代码中onTouchEvent()中返回true or false的区别。

 

mRV.setOnTouchListener((v, event) -> {
    Log.e("试图传递","布局  onTouch");
      return false;
});

mRV.setOnClickListener(v -> Log.e("试图传递","布局  OnClick"));

mBtn.setOnTouchListener((v, event) -> {
     Log.e("试图传递","按钮 onTouch");
     // 通过改变onTouchEvent的值,去进行修改
     return false;
 });

mBtn.setOnClickListener(v -> Log.e("试图传递","按钮  OnClick"));

MyDvRelativeLayout.xml

public class MyDvRelativeLayout extends RelativeLayout {
    public MyDvRelativeLayout(Context context) {
        super(context);
    }

    public MyDvRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyDvRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("事件分发","父ViewGroup dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("事件分发","父ViewGroup onInterceptTouchEvent   "+super.onInterceptTouchEvent(ev));
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("事件分发","父ViewGroup onTouchEvent");
        return super.onTouchEvent(event);
    }
}

MyButton.xml

public class MyButton extends android.support.v7.widget.AppCompatButton {
    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("事件分发","子View dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("事件分发","子View onTouchEvent 返回: "+super.onTouchEvent(event) +" 触摸状态 "+event.getAction() );
        return super.onTouchEvent(event);
    }

}

当onTouchEvent 返回true或者默认效果,点击按钮运行结果:

如下图可以发现:布局并未响应点击事件,事件的流向打印如下:

07-19 14:01:54.354 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:01:54.354 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
07-19 14:01:54.354 15257-15257/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
07-19 14:01:54.356 15257-15257/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 返回: true 触摸状态 0
07-19 14:01:54.426 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:01:54.426 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
07-19 14:01:54.426 15257-15257/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
07-19 14:01:54.427 15257-15257/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 返回: true 触摸状态 1

按钮的响应如下,只触发了按钮的事件,正如上图所示,由于子view的onTouchEvent( )返回了true了,那么就意味着触摸事件已经被消费了,那么就不会返回给父控件再进行处理了 

07-19 14:04:04.141 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 onTouch
07-19 14:04:04.199 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 onTouch
07-19 14:04:04.200 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 OnClick
07-19 14:04:04.201 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 OnClick

合起来如下:

07-19 14:06:49.395 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:06:49.395 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
07-19 14:06:49.395 15257-15257/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
07-19 14:06:49.395 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 onTouch
07-19 14:06:49.397 15257-15257/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 返回: true 触摸状态 0
07-19 14:06:49.434 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:06:49.434 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
07-19 14:06:49.434 15257-15257/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
07-19 14:06:49.434 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 onTouch
07-19 14:06:49.434 15257-15257/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 返回: true 触摸状态 2
07-19 14:06:49.483 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:06:49.483 15257-15257/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
07-19 14:06:49.483 15257-15257/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
07-19 14:06:49.483 15257-15257/com.example.mydairytestproject E/试图传递: 按钮 onTouch
07-19 14:06:49.484 15257-15257/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 返回: true 触摸状态 1
07-19 14:06:49.484 15257-15257/com.example.mydairytestproject E/试图传递: 按钮  OnClick
07-19 14:06:49.487 15257-15257/com.example.mydairytestproject E/试图传递: 按钮  OnClick

当MyButton的 onTouchEvent()如下返回false时,打印日志如下

@Override
public boolean onTouchEvent(MotionEvent event) {
   Log.e("事件分发","子View onTouchEvent 返回: "+super.onTouchEvent(event) +" 触摸状态 "+event.getAction() );
   return false;
}
07-19 14:12:26.425 18319-18319/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:12:26.425 18319-18319/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
07-19 14:12:26.425 18319-18319/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
07-19 14:12:26.426 18319-18319/com.example.mydairytestproject E/试图传递: 按钮 onTouch
07-19 14:12:26.427 18319-18319/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 返回: true 触摸状态 0
07-19 14:12:26.427 18319-18319/com.example.mydairytestproject E/试图传递: 布局  onTouch
07-19 14:12:26.427 18319-18319/com.example.mydairytestproject E/事件分发: 父ViewGroup onTouchEvent
07-19 14:12:26.496 18319-18319/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:12:26.496 18319-18319/com.example.mydairytestproject E/试图传递: 布局  onTouch
07-19 14:12:26.496 18319-18319/com.example.mydairytestproject E/事件分发: 父ViewGroup onTouchEvent
07-19 14:12:26.497 18319-18319/com.example.mydairytestproject E/试图传递: 布局  OnClick

onTouchListener 返回true

此时不会执行点击事件 也不会执行onTouchevent

2019-11-07 14:29:23.744 16487-16487/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
2019-11-07 14:29:23.744 16487-16487/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
2019-11-07 14:29:23.744 16487-16487/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
2019-11-07 14:29:23.745 16487-16487/com.example.mydairytestproject E/事件分发: onTouch: 0
2019-11-07 14:29:23.937 16487-16487/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
2019-11-07 14:29:23.937 16487-16487/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
2019-11-07 14:29:23.937 16487-16487/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
2019-11-07 14:29:23.937 16487-16487/com.example.mydairytestproject E/事件分发: onTouch: 1

下面看看默认的情况吧

2019-11-07 14:37:37.618 17188-17188/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
2019-11-07 14:37:37.618 17188-17188/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
2019-11-07 14:37:37.618 17188-17188/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
2019-11-07 14:37:37.618 17188-17188/com.example.mydairytestproject E/事件分发: onTouch: 0
2019-11-07 14:37:37.621 17188-17188/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 默认返回: true 触摸状态 0
2019-11-07 14:37:38.597 17188-17188/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
2019-11-07 14:37:38.597 17188-17188/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   false
2019-11-07 14:37:38.597 17188-17188/com.example.mydairytestproject E/事件分发: 子View dispatchTouchEvent
2019-11-07 14:37:38.597 17188-17188/com.example.mydairytestproject E/事件分发: onTouch: 1
2019-11-07 14:37:38.597 17188-17188/com.example.mydairytestproject E/事件分发: 子View onTouchEvent 默认返回: true 触摸状态 1
2019-11-07 14:37:38.598 17188-17188/com.example.mydairytestproject E/事件分发: OnClickListener:

 

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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;
            }
        }

 

onTouchEvent()小结

点击事件是在手指抬起时触发的,只有OnTouchEvent()默认一般情况下是返回true的,意味着事件被该控件(布局)给消费了,如果返回 false 那么意味着无法消费,那么就不得不交给父布局处理。

接下啦来把父视图的 onInterceptTouchEvent( )返回 true,即拦截事件看看,Button啥都没调用

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
      return true;
 }
07-19 14:25:00.493 21372-21372/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:25:00.493 21372-21372/com.example.mydairytestproject E/事件分发: 父ViewGroup onInterceptTouchEvent   true
07-19 14:25:00.493 21372-21372/com.example.mydairytestproject E/试图传递: 布局  onTouch
07-19 14:25:00.493 21372-21372/com.example.mydairytestproject E/事件分发: 父ViewGroup onTouchEvent
07-19 14:25:00.563 21372-21372/com.example.mydairytestproject E/事件分发: 父ViewGroup dispatchTouchEvent
07-19 14:25:00.564 21372-21372/com.example.mydairytestproject E/试图传递: 布局  onTouch
07-19 14:25:00.564 21372-21372/com.example.mydairytestproject E/事件分发: 父ViewGroup onTouchEvent
07-19 14:25:00.565 21372-21372/com.example.mydairytestproject E/试图传递: 布局  OnClick

在下面的博文1中,get到了多点触控的知识,像每增加一个触控点就会增一个pointer来描述这个触控点,

值得学习的概念基础:

这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

1. Android MotionEvent详解

 

如上,我们对view的 dispatchTouchEvent  onTouchEvent 以及 viewGroup的 dispatchTouchEvent  onInterceptTouchEvent onTouchEvent 都是有所了解的。那么针对具体的点击事件,为什么是该过程呢,那么我们就应该去看源码了。

 

首先默认 第一个方法调用的是 dispatchTouchEvent , 为什么先调这个后续可以去研究。

view$dispatchTouchEvent:

    public boolean dispatchTouchEvent(MotionEvent event) {
        // ......

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            /**
               如果设立点击事件,且是enable,而且调用 onTouchListener 其返回值为 true,
             那么派发结束,这时候并不会执行 view 的 onTouchEvent
             **/
            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$onTouchEvent

                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                /**
                                    通过消息队列发送消息,进行调用
                                    如果加入队列失败,则直接调用
                                 **/    
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

view$performClick

    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

viewGroup$dispaTouchEvent

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    // 
                    // 此处调用了 viewGroup自己的 onInterceptTouchEvent
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            }
            ........


            if (!canceled && !intercepted) {
                // ......
                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
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        // 遍历儿子 分别进行事件派发
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            //.....
                            //.....
                            // 怎么样 到这里了吧    
                            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;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    // .....
                }
            }


}
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // 。。。。。。  
        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;
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值