开发一段时间的Android,或多或少对Android的事件有一些了解,对诸如dispatchTouchEvent、onTouchEvent方法有些了解。但真正在面试中被问起,整个机制,或者具体的分析ViewGroup+ViewGroup+View的具体回调顺序,就懵了。百度出的第一位博客讲解的很到位:

当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。

ViewGroup+ViewGroup+View事件传递的具体分析

题目:比如最上面有一个ViewGroup,下面有一个ViewGroup,最下面有一个Button,那么点击Button,请问事件是如何传递的,这期间会回调哪些方法,顺序如何?
看了上面的讲解,其实心中已有一定的了解。但不能止于此。于是我使用代码验证并查看Android的源代码来看看到底是怎么回事,来找到最后的答案。
新建Activity,layout的结构为LinearLayout - RelativeLayout - Button,刚好是题目所示的结构。为了验证回调函数,我将这三个View都继承了,同时也在Activity中覆盖dispatchTouchEventonTouchEvent

layout布局如下:

<?xml version="1.0" encoding="utf-8"?><com.idengpan.life100.viewtouch.TouchLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical" >    <com.idengpan.life100.viewtouch.TouchRelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent" >        <com.idengpan.life100.viewtouch.TouchButton            android:id="@+id/btn_test"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="测试" />    </com.idengpan.life100.viewtouch.TouchRelativeLayout></com.idengpan.life100.viewtouch.TouchLinearLayout>

三个自定义的View/ViewGroup如下:

public class TouchLinearLayout extends LinearLayout {    public TouchLinearLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        System.out.println("TouchLinearLayout onInterceptTouchEvent");        boolean result = super.onInterceptTouchEvent(ev);        System.out.println("TouchLinearLayout onInterceptTouchEvent return " + result);        return result;    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        System.out.println("TouchLinearLayout dispatchTouchEvent");        boolean result = super.dispatchTouchEvent(ev);        System.out.println("TouchLinearLayout dispatchTouchEvent return " + result);        return result;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        System.out.println("TouchLinearLayout onTouchEvent");        boolean result = super.onTouchEvent(event);        System.out.println("TouchLinearLayout onTouchEvent return " + result);        return result;    }}public class TouchRelativeLayout extends RelativeLayout{    public TouchRelativeLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        System.out.println("TouchRelativeLayout onInterceptTouchEvent");        boolean result = super.onInterceptTouchEvent(ev);        System.out.println("TouchRelativeLayout onInterceptTouchEvent return " + result);        return result;    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        System.out.println("TouchRelativeLayout dispatchTouchEvent");        boolean result = super.dispatchTouchEvent(ev);        System.out.println("TouchRelativeLayout dispatchTouchEvent return " + result);        return result;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        System.out.println("TouchRelativeLayout onTouchEvent");        boolean result = super.onTouchEvent(ev);        System.out.println("TouchRelativeLayout onTouchEvent return " + result);        return result;    }}public class TouchButton extends Button {    public TouchButton(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        System.out.println("TouchButton dispatchTouchEvent");        boolean result = super.dispatchTouchEvent(ev);        System.out.println("TouchButton dispatchTouchEvent return " + result);        return result;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        System.out.println("TouchButton onTouchEvent");        boolean result = super.onTouchEvent(ev);        System.out.println("TouchButton onTouchEvent return " + result);        return result;    }}//Activity中覆盖:@Override    public boolean dispatchTouchEvent(MotionEvent ev) {        System.out.println("activity dispatchTouchEvent");        boolean result = super.dispatchTouchEvent(ev);        System.out.println("activity dispatchTouchEvent return " + result);        return result;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        System.out.println("activity onTouchEvent");        boolean result = super.onTouchEvent(ev);        System.out.println("activity onTouchEvent return " + result);        return result;    }

点击Button,可以看到回调的执行顺序是:

 I/System.out(7034): activity dispatchTouchEvent I/System.out(7034): TouchLinearLayout dispatchTouchEvent I/System.out(7034): TouchLinearLayout onInterceptTouchEvent I/System.out(7034): TouchLinearLayout onInterceptTouchEvent return false I/System.out(7034): TouchRelativeLayout dispatchTouchEvent I/System.out(7034): TouchRelativeLayout onInterceptTouchEvent I/System.out(7034): TouchRelativeLayout onInterceptTouchEvent return false I/System.out(7034): TouchButton dispatchTouchEvent I/System.out(7034): TouchButton onTouchEvent I/System.out(7034): TouchButton onTouchEvent return true I/System.out(7034): TouchButton dispatchTouchEvent return true I/System.out(7034): TouchRelativeLayout dispatchTouchEvent return true I/System.out(7034): TouchLinearLayout dispatchTouchEvent return true I/System.out(7034): activity dispatchTouchEvent return true

看到日志输出,更是一目了然了。去查看了下ViewGroup的源码,它的onInterceptTouchEvent方法未做任何处理,总是返回false的。

public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;}

再看看ViewGroup中的dispatchTouchEvent方法:

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) {                    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;            }...//dispatchTransformedTouchEvent方法中 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;        }...}}

那View里的dispatchTouchEvent方法执行的很明显,直接实现onTouch监听或执行onTouchEvent方法:

 public boolean dispatchTouchEvent(MotionEvent event) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                return true;            }            if (onTouchEvent(event)) {                return true;            }        }        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        return false;    }

ViewGroup中没有覆盖onTouchEvent方法,都是执行View中的onTouchEvent方法。
在View的onTouchEvent方法中,会在ACTION_UP事件中调用performCLick方法,从而响应Button.onClick事件:

public boolean onTouchEvent(MotionEvent event) {    ...    switch (event.getAction()) {                case MotionEvent.ACTION_UP:                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                    if ((mPrivateFlags & 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);                       }                        if (!mHasPerformedLongPress) {                            // 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();                                }                            }                        }                        ....}

所以这个题目的答案就是,点击事件从Activity传递到ViewGroup,最上层的ViewGroup调用dispatchTouchEvent进行事件分发,接着会调用下面一层ViewGroup的dispatchTouchEvent方法,然后就是Button的dispatchTouchEvent方法,而Button的dispatchTouchEvent方法会调用Button的onTouchEvent方法,在这个方法里,点击事件被消费,返回了true,接着Button的dispatchTouchEvent方法也返回true,然后中间ViewGroup的dispatchTouchEvent方法也返回true,然后是最上层的dispatchTouchEvent方法也返回true,最后是Activity的dispatchTouchEvent方法也返回true。事件一步一步向下传递,然后一步一步向上冒泡反馈。

参考文章:http://www.cnblogs.com/linjzong/p/4191891.html

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android(安卓)Wifi模块分析(三)
  7. Android中dispatchDraw分析
  8. Android四大基本组件介绍与生命周期
  9. Android(安卓)MediaPlayer 常用方法介绍

随机推荐

  1. android的四种启动方式和各自特点
  2. Android(安卓)Jetpack简介
  3. Android:解析 SurfaceView & TextureView
  4. Android 8.0 SystemUI下拉状态栏快捷开关
  5. Android开发者官网:Android 4.4发布10个月
  6. 第2步:第一个“Hello,world!”之Android(
  7. Android移植到mini2440(进行中)
  8. Android开发人员资料大全(开发人员必看)
  9. Android(安卓)Studio添加Parcelable序列
  10. android的binder机制研究