Android 事件分发机制分析

来源:互联网 发布:pc蛋蛋幸运28算法技巧 编辑:程序博客网 时间:2024/06/01 22:21

在Android开发中,我们经常会遇到滑动冲突的情况,当遇到这种情况我们要怎么去解决它,那就需要弄明白事件分发的过程以及原理,这里我先画出了整个事件分发过程的流程图:
这里写图片描述

注释:

  • 上面流程图中的super, true, false 字代表返回值(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
  • dispatchTouchEvent和 onTouchEvent的框里有个【true—->消费】的字,表示如果它们的返回值是true,那么事件就在此消费,不会在往别的地方传了,即事件终止。

从上面流程图可以看出整个事件流向应该是从Activity—->ViewGroup—>View 从上往下调用dispatchTouchEvent方法,一直到最后一个View的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法;而且可以看出事件的分发过程是由三个很重要的方法来共同完成的:

  • dispatchTouchEvent()
    该方法是用来处理事件的分发。如果事件能够传递到当前View,那么一定会调用此方法;它的返回值是个boolean类型:
    如果返回true,表示事件就在此消费,不会在往别的地方传,即事件终止 ;
    如果返回false,那么事件将传递到它的父View中, 并且父View执行onTouchEvent方法;
    如果返回super.dispatchTouchEvent(ev),这里分两种情况,如果当前控件是View,那么执行当前View的onTouchEvent的方法;如果当前控件是ViewGroup, 那么将执行当前ViewGroup的onInterceptTouchEvent() 方法,询问是否拦截。

  • onInterceptTouchEvent()
    只有ViewGroup中有这个方法,Activity和View都没有此方法。通过ViewGroup源码我们知道,onInterceptTouchEvent是在dispatchTouchEvent方法中调用的,来判断自己是否需要拦截此事件;它的返回值是个boolean类型:
    如果返回true, 表示拦截,然后执行当前控件的onTouchEvent方法;
    如果返回false和返回super.onInterceptTouchEvent(ev),表示不拦截 , 事件继续向下传递(即传递到当前控件下子View的dispatchTouchEvent方法)。

  • onTouchEvent()
    和onInterceptTouchEvent()一样也是在dispatchTouchEvent中调用的。用来处理点击事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP。它的返回值是个boolean类型:
    如果返回true,表示消费当前view的onTouchEvent事件,不在向上传递;如果当前容器是ViewGroup,它的onTouchEvent返回true,那么后续执行move,up时 ,它的onInterceptTouchEvent不在执行;
    如果返回false,表示不消费当前view的onTouchEvent事件,那么事件将传递到它的父View中, 并且父View执行onTouchEvent方法;
    如果返回super.onTouchEvent(event),这里分两种情况,如果这个view是可点击的(比如:button),也就是clickable或者longClickable为true(View的longClickable默认为false),这种情况super.onTouchEvent(event)返回的是true,表示消费当前view的onTouchEvent事件,不在向上传递;如果这个view是不可点击的(比如:TextView),也就是clickable和longClickable都为false,这种情况super.onTouchEvent(event)返回的是false,表示不消费当前view的onTouchEvent事件,继续向上传递(即传递到当前控件的父View的onTouchEvent方法)。

    在这里要强调View的OnTouchListener。如果View设置了该监听,那么OnTouch()将会回调。如果返回为true,那么该View的OnTouchEvent将不会在执行,从源码(截取关键代码)中我们也可以看出来:

   /**     * Pass the touch screen motion event down to the target view, or this     * view if it is the target.     *     * @param event The motion event to be dispatched.     * @return True if the event was handled by the view, false otherwise.     */    public boolean dispatchTouchEvent(MotionEvent event) {        .....        if (onFilterTouchEventForSecurity(event)) {           .....            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }        }        }

上面代码中,我们可以看到当li.mOnTouchListener.onTouch(this, event)返回true时,那么程序就会走到if方法体里面,这时直接返回了true,所以下面的OnTouchEvent代码就没有执行到,所以如果你设置了OnClickListener监听,onClick里面的方法也不会执行。

好了,上面对dispatchTouchEvent,OnInterceptTouchEvent ,OnTouchEvent方法做了详细讲解,下面我们通过事例来更进一步的理解事件分发的过程,我们这里新建一个项目,布局如图:

这里写图片描述

ViewGroupA嵌套ViewGroupB , 然后在嵌套ViewC,然后在相应的控件中重写dispatchTouchEvent,OnInterceptTouchEvent ,OnTouchEvent方法(View中没有OnInterceptTouchEvent),这里在Activity也重写dispatchTouchEvent,OnTouchEvent方法,并在每个方法中输出相应的Log:

Activity中:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("lw", "Activity----->dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("lw", "Activity----->onTouchEvent");        return super.onTouchEvent(event);    }}

ViewGroupA中:

public class ViewGroupA extends LinearLayout{    public ViewGroupA(Context context) {        super(context);    }    public ViewGroupA(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public ViewGroupA(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("lw", "ViewGroupA----->dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.d("lw", "ViewGroupA----->onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("lw", "ViewGroupA----->onTouchEvent");        return super.onTouchEvent(event);    }}

ViewGroupB中:

public class ViewGroupB extends LinearLayout{    public ViewGroupB(Context context) {        super(context);    }    public ViewGroupB(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public ViewGroupB(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("lw", "ViewGroupB----->dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.d("lw", "ViewGroupB----->onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("lw", "ViewGroupB----->onTouchEvent");        return super.onTouchEvent(event);    }}

ViewC中:

public class ViewC extends View {    public ViewC(Context context) {        super(context);    }    public ViewC(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ViewC(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("lw", "ViewC----->dispatchTouchEventC");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("lw", "ViewC----->onTouchEventC");        return super.onTouchEvent(event);    }}

不做任何修改,运行程序,点击ViewC:

这里写图片描述

从log信息中可以看出,事件传递顺序是:
Activity ——> ViewGroup ——-> View; 首先会先执行Activity的 dispatchTouchEvent方法,然后执行ViewGroupA的dispatchTouchEvent方法,接着会调用onInterceptTouchEvent方法来判断是否需要拦截事件,默认是不拦截的,接着事件又会传递到ViewGroupA的子View(ViewGroupB)。即ViewGroupB 的dispatchTouchEvent方法被调用,直到ViewC,当事件传递到ViewC后,ViewC的dispatchTouchEvent方法又会执行。此时如果ViewC 不消费该事件,也就是说ViewC的onTouchEvent方法返回false,那么事件会向上传递给它的父View(ViewGroupB)的OnTouchEvent方法事件处理,以此类推。

将MainActivity中dispatchTouchEvent方法的返回值改成true,然后点击ViewC:

这里写图片描述

从log信息中可以看出,将MainActivity中dispatchTouchEvent方法的返回值改成true后,事件传递到
dispatchTouchEvent就终止了,不在往任何地方传递了。

将MainActivity中dispatchTouchEvent方法的返回值改成false,然后点击ViewC:

这里写图片描述

从log信息中可以看出,将MainActivity中dispatchTouchEvent方法的返回值改成false和返回值改成true都是一样的效果,都是终止事件的传递。

将ViewGroupB中dispatchTouchEvent方法的返回值改成true,然后点击ViewC:

这里写图片描述

从log信息中可以看出,将MainActivity中dispatchTouchEvent方法的返回值改成true后,事件传递到
dispatchTouchEvent就终止了,不在往任何地方传递了。

从log信息中可以看出,将ViewGroupB中dispatchTouchEvent方法的返回值改成true后,事件传递到
dispatchTouchEvent就终止了,不在往任何地方传递了。

将ViewGroupB中dispatchTouchEvent的返回值改成false,然后点击ViewC:

这里写图片描述

从log信息中可以看出,将ViewGroupB中dispatchTouchEvent方法的返回值改成false后,事件传递到
dispatchTouchEvent,然后又将事件向上传递给了ViewGroupA(即它的父元素)的onTouchEvent方法。

将ViewGroupB中onInterceptTouchEvent的返回值改成true,然后点击ViewC:

这里写图片描述

从log信息中可以看出,将ViewGroupB中onInterceptTouchEvent方法的返回值改成true后,事件没有向下传递,而是执行了当前ViewGroupB中的onTouchEvent方法。

将ViewGroupB中onInterceptTouchEvent的返回值改成false,然后点击ViewC:

这里写图片描述

看log没啥好说的,将ViewGroupB中onInterceptTouchEvent的返回值改成false,和将ViewGroupB中onInterceptTouchEvent的返回值改成super.onInterceptTouchEvent(ev)一样的效果,都表示不拦截事件,继续向下传递。

将ViewC中onTouchEvent的返回值改成true,然后点击ViewC:

这里写图片描述

从log信息中可以看出 , 将ViewC中onTouchEvent方法的返回值改成true后,事件就在此消费,不在向上传递了,事件终止。

将ViewC中onTouchEvent的返回值改成false,然后点击ViewC:

这里写图片描述

从log信息中可以看出 , 将ViewC中onTouchEvent方法的返回值改成false后,事件不消费,并且将事件向上传递给了ViewGroupB(它的父View)的onTouchEvent方法。

了解下requestDisallowInterceptTouchEvent:
requestDisallowInterceptTouchEvent方法是子View用来告诉父容器不要拦截子View事件的。
下面在ViewC代码中中,添加requestDisallowInterceptTouchEvent方法:

public class ViewC extends View {    public ViewC(Context context) {        super(context);    }    public ViewC(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ViewC(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("lw", "ViewC----->dispatchTouchEventC");        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.d("lw", "ViewC dispatchTouchEvent ACTION_DOWN");                getParent().requestDisallowInterceptTouchEvent(true);                break;            case MotionEvent.ACTION_MOVE:                Log.d("lw", "ViewC dispatchTouchEvent ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                getParent().requestDisallowInterceptTouchEvent(false);                Log.d("lw", "ViewC dispatchTouchEvent ACTION_UP");                break;            default:                break;        }        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("lw", "ViewC----->onTouchEventC");        return true;    }}

在ViewC中添加requestDisallowInterceptTouchEvent相关代码,然后点击ViewC:

这里写图片描述

首先我们要先消费当前View,即让onTouchEvent返回时true,将事件交给当前View处理,这样后续的action(move,up)才会执行。
requestDisallowInterceptTouchEvent(true)表示当前View的所有父ViewGroup都会跳过onInterceptTouchEvent方法,父ViewGroup执行dispatchTouchEvent后,不在询问是否拦截,直接传递到子view,子view执行dispatchTouchEvent,上面的log也证明了这一点。
requestDisallowInterceptTouchEvent(false)与true的情况相反,它不会跳过onInterceptTouchEvent方法,父ViewGroup执行dispatchTouchEvent后,也会执行onInterceptTouchEvent方法,询问是否拦截法。

好了 ,这样View的整个事件分发的流程我们就差不多清楚了,不过这里需要注意的是,如果你在执行ACTION_DOWN的时候返回了false,那么后面一系列其它的action(move,up)就不会再执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

原创粉丝点击