Android面试——事件的传递机制

来源:互联网 发布:连接其它机器的mysql 编辑:程序博客网 时间:2024/06/07 19:23

Android事件的传递机制

前言

最近面试了很多公司,大多数公司都问到了这么一个问题,就是Android事件的传递机制,那Android事件的传递机制到底是怎么一回事?今天我们来探讨探讨!

正文

Android中三个方法是关于事件传递的,分别是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,那这三个方法分别有什么用呢?其实我们可以从字面上的意思可以理解分别是分配触摸事件、截断触摸事件以及响应触摸事件,那这三个方法到底在事件的传递机制中扮演着什么角色?我们一起可以来看看源码!在看源码前,我们先明确事件的基本传递顺序和哪些类中有哪些触摸事件的方法!(后面会具体证明)传递顺序是从最外层传递到最里层,例如:Activity - - > LinearLayout - - > TextView,下面是哪些类中有哪些事件方法

类 相关子类 方法 Activity类 Activity…… dispatchTouchEvent(); onTouchEvent(); View容器(ViewGroup的子类) FrameLayout、LinearLayout、ListView、ScrollVIew…… dispatchTouchEvent(); onInterceptTouchEvent(); onTouchEvent(); View控件(非ViewGroup子类) Button、TextView、EditText…… dispatchTouchEvent(); onTouchEvent();

其中onInterceptTouchEvent只有ViewGroup有,为什么ViewGroup才有onInterceptTouchEvent方法,因为他是截断事件,而截断事件不可能存在Activity中与View中,在Activity中,那你截取触摸事件干什么呢?在Activity中将事件截取了,那Activity中的布局控件就获取不到触摸事件,那相当于没有布局上控件的什么事!所以Activity中Google官方没有给Activity设置onInterceptTouchEvent方法,那View中为什么没有该方法呢?那是因为View本来就是最底层了,View没有下一层的子控件了,不需要向下传递事件,你截取事件与不截取事件都是一样的,所以也没有onInterceptTouchEvent的方法!

下面我们重写三个类,分别是FrameLayout、LinearLayout、TextView

FrameLayout

public class MyFrameLayout extends FrameLayout{    public MyFrameLayout(@NonNull Context context) {        super(context);    }    public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Logger.e("Leezp", "MyFrameLayout调用dispatchTouchEvent分配任务");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Logger.e("Leezp", "MyFrameLayout调用onInterceptTouchEvent是否阻止任务?");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Logger.e("Leezp", "MyFrameLayout调用onTouchEvent是否处理了任务?");        return super.onTouchEvent(event);    }}

LinearLayout

public class MyLinearLayout extends LinearLayout{    public MyLinearLayout(Context context) {        super(context);    }    public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Logger.e("Leezp", "MyLinearLayout调用dispatchTouchEvent分配任务");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Logger.e("Leezp", "MyLinearLayout调用onInterceptTouchEvent是否阻止任务?");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Logger.e("Leezp", "MyLinearLayout调用onTouchEvent是否处理了任务?");        return super.onTouchEvent(event);    }}

TextView

public class MyTextView extends android.support.v7.widget.AppCompatTextView{    public MyTextView(Context context) {        super(context);    }    public MyTextView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Logger.e("Leezp", "MyTextView调用dispatchTouchEvent分配任务");        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Logger.e("Leezp", "MyTextView调用onTouchEvent是否处理了任务?");        return super.onTouchEvent(event);    }}

然后在MainActivity中将其所对用的dispatchTouchEvent、onTouchEvent两个方法重写一下

 @Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    Logger.e("Leezp", "MainActivity调用dispatchTouchEvent分配任务");    return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {    Logger.e("Leezp", "MainActivity调用onTouchEvent是否处理了处理任务?");    return super.onTouchEvent(event);}

这儿的Logger是我自己写的一个Log日志工具,你们可以直接用Log代替!

然后在activity_main布局中

<?xml version="1.0" encoding="utf-8"?><com.android.leezp.toucheventtransfer.utils.MyFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/colorAccent"    tools:context="com.android.leezp.toucheventtransfer.activities.MainActivity">    <com.android.leezp.toucheventtransfer.utils.MyLinearLayout        android:layout_width="match_parent"        android:layout_height="200dp"        android:layout_gravity="center"        android:background="@color/colorPrimary"        android:gravity="center"        android:orientation="vertical">        <com.android.leezp.toucheventtransfer.utils.MyTextView            android:layout_width="match_parent"            android:layout_height="100dp"            android:background="@color/colorBlack"            android:gravity="center"            android:text="事件传递"            android:textColor="#ffffff"            android:textSize="15sp" />    </com.android.leezp.toucheventtransfer.utils.MyLinearLayout></com.android.leezp.toucheventtransfer.utils.MyFrameLayout>

展示图:
这里写图片描述

最外层红色的是个FrameLayout,蓝色的是LinearLayout,黑色的是TextView

然后我们运行该程序,点击其中黑色区域

这里写图片描述

我们可以看见事件的经过,但是事件怎么调用了两次MainActivity的dispatchTouchEvent的方法呢?

其实我们的事件分三种,一种是down,手指接触屏幕,一种是move,手指在屏幕上移动,一种是up,手指离开屏幕,而我们点击事件只有down与up,而在down的过程中,事件会依次去按照传递过程中的步骤从最外层到最里层,然后获取到他这个事件在哪一层被处理了,然后下一次的up事件就只传到该层处理就行了,不需要继续传递,因为这次down事件所有的类都没处理,所以up事件就传到最外层的MainActivity中,然后进行判断是否处理该事件就结束了,这就是一次点击事件怎么调用了两次MainActivity的dispatchTouchEvent的方法

然后我们来看一下Activity中dispatchTouchEvent方法的源码

    /**     * Called to process touch screen events.  You can override this to     * intercept all touch screen events before they are dispatched to the     * window.  Be sure to call this implementation for touch screen events     * that should be handled normally.     *     * @param ev The touch screen event.     *     * @return boolean Return true if this event was consumed.     */    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }

我们可以看到,他会先去判断事件的动作是否是down,如果是down的话,我们可以通过重写onUserInteraction中写入自己的一些界面交互的提示之类的信息,下面是Activity中自带的onUserInteraction方法

    /**     * Called whenever a key, touch, or trackball event is dispatched to the     * activity.  Implement this method if you wish to know that the user has     * interacted with the device in some way while your activity is running.     * This callback and {@link #onUserLeaveHint} are intended to help     * activities manage status bar notifications intelligently; specifically,     * for helping activities determine the proper time to cancel a notfication.     *     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will     * be accompanied by calls to {@link #onUserInteraction}.  This     * ensures that your activity will be told of relevant user activity such     * as pulling down the notification pane and touching an item there.     *     * <p>Note that this callback will be invoked for the touch down action     * that begins a touch gesture, but may not be invoked for the touch-moved     * and touch-up actions that follow.     *     * @see #onUserLeaveHint()     */    public void onUserInteraction() {    }

然后就是他会通过superDispatchTouchEvent方法去Activity布局中分配事件,也就是调用子控件的dispatchTouchEvent方法,如果有控件处理了这个事件,他就会返回true,如果没有,它会调用自己的onTouchEvent方法,这就是Activity中的事件处理机制

ViewGroup中的事件处理机制大体差不多,不过代码有点多,如果想深入研究的,也可以看看,也就是父级控件先去调用ViewGroup的dispatchTouchEvent方法,然后判断onInterceptTouchEvent是否截断该事件,如果截断不往下传递,然后调用自己的onTouchEvent方法,如果没有截取该事件,那就继续往下传!

View就更不用说了,也是父级控件调用他的dispatchTouchEvent方法,在dispatchTouchEvent中调用自己的onTouchEvent方法!

总结

  1. dispatchTouchEvent方法就是分配任务的一个方法,如果是Activity,他会先去调用子级的dispatchTouchEvent,如果是ViewGroup,他会先调用自己的onInterceptTouchEvent方法,然后再调用子级的dispatchTouchEvent,不过Activity、ViewGroup、View在这个方法中都有个共同的特征,如果子级没处理,都会调用自己的onTouchEvent方法

  2. onInterceptTouchEvent方法就是ViewGroup进行截断事件向下传递的方法

  3. onTouchEvent方法就是类对事件处理的方法

  4. 事件传递机制是从父级到子级一层一层往下传,截断了该事件就不会往下传了,然后会一层一层的返回是否已经处理了该事件了,如果处理了,需返回true,没处理会返回false,所以面试官有时会问你如果截取了事件,是否往下传,这当然是不会往下传了,还可能问你那我截取了事件并处理了该事件,那还会返回父级吗?当然会返回,因为父级要确认是否子级已经处理该事件!

参考:

http://blog.csdn.net/morgan_xww/article/details/9372285/

http://www.cnblogs.com/linjzong/p/4191891.html