zoukankan      html  css  js  c++  java
  • 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事件进行拦截;

  • 相关阅读:
    快速设置Scrapy随机的IP代理
    快速设置随机的UserAgent
    windows安装flask并创建虚拟环境
    数电和模电比较
    程序员如何写出一份好的文档?
    Qt简介
    基于 Qt的聊天工具
    如何将word组合图形保存成png
    2015电赛点滴
    函数定义
  • 原文地址:https://www.cnblogs.com/lianzhen/p/11276961.html
Copyright © 2011-2022 走看看