开发一段时间的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. Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
  2. Android解析Intent Filter的方法
  3. android中访问本机服务器的方法
  4. Android中使用imageviewswitcher 实现图片切换轮播导航的方法
  5. Android NDK环境创建方法简介
  6. Unity3D和Android之间的方法交互(jar模式)

随机推荐

  1. Unity出现 error building player except
  2. 如何掌握Android-Camera模块
  3. ListView中点击事件无法响应
  4. Android 模拟器上安装APK步骤
  5. android:configChanges的用法
  6. Android代码内存优化建议-OnTrimMemory优
  7. Android(安卓)Studio Error -- Could not
  8. android 开发过程中遇到问题
  9. TextView属性
  10. Android 2.1 Compatibility Definition