前言

android事件分发机制是一个非常重要但是比较难理解的知识点,大部分自定义控件等功能都用到了事件分发机制,搞懂这方面的知识还是很有必要的,所有一直想总结一下android事件分发机制的原理,一来方便自己以后查看,二来加深自己的印象。网上这方面资料也比较多,我也参考了一下大神的文章,自己总结一下。

分析

什么是事件分发?

我们在操作手机屏幕的时候,每一个点击、移动动作都看做是一个事件,android屏幕捕捉到这些事件后,把这些事件一层一层的分发到下面的控件,由控件来决定是否对这个事件进行响应处理。

那么android是怎么进行事件分发的呢?

整个的一个Window页面,最顶层是DecorView,DecorView只有一个LinearLayout,是一个ViewGroup,android控件都是继承View的,ViewGroup也是继承View的,所以我们屏幕的Touch操作,其实就是调用内部的ViewGroup的Touch事件。

View在ViewGroup中,ViewGroup也可以在其他ViewGroup中:


上面就是ViewGroup结构图,在结束时间分发前,我们先要要了解View和ViewGroup的相关touch事件方法:

touch事件方法:

dispatchTouchEventMotionEvent ev):事件分发,以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:

  • 如果 return true,事件会分发给当前 View并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
  • 如果 return false,事件分发给下一层的View,如果下层没有View,就交给父View的onTouchEvent进行处理
  • 如果返回系统默认的 super.dispatchTouchEvent(ev),如果是ViewGroup事件会自动的分发给当前Viewgroup 的 onInterceptTouchEvent 方法,如果是View的话分发给当前View的onTouchEvent 进行处理

onInterceptTouchEventMotionEvent ev):事件拦截,只有ViewGroup有这个方法,View是没用的,因为View是最后一层,不用拦截。onInterceptTouchEvent的处理逻辑:

  • 如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
  • 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
  • 如果 onInterceptTouchEvent 返回super.onInterceptTouchEvent(ev),事件默认不被拦截,往下传递。

onTouchEventMotionEvent ev): 事件响应,ViewGroup在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或View在dispatchTouchEvent 返回super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:

  • 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
  • 如果返回了 true 则会接收并消费该事件。

  • 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。

requestDisallowInterceptTouchEvent(boolean flag):设置事件是否可以拦截,只有在ViewGroup中才有这个方法,View中没有。我们在子View的dispatchTouchEvent中添加getParent().requestDisallowInterceptTouchEvent(true)的,就可以防止我们View的事件被上层ViewGroup拦截。

介绍了所有有关的Touch方法,那我们根据上面的图来了解一下流程:

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

案例

我们来写一个案例来加深我们的理解,自定义一个View和ViewGroup来看看它们之间事件是怎么进行分发的。

自定义的View:

package com.test.touch;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.Button;/** * Created by fuweiwei on 2016/1/20. */public class MyButton extends Button {    private static final  String TAG="MyButton";    public MyButton(Context context) {        super(context);    }    public MyButton(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int action = event.getAction();        switch (action)        {            case MotionEvent.ACTION_DOWN:                Log.v(TAG, "onTouchEvent;----> ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.v(TAG, "onTouchEvent;----> ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.v(TAG, "onTouchEvent;----> ACTION_UP");                break;            default:                break;        }        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.v(TAG, "dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }}

自定义的ViewGroup:

package com.test.touch;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.LinearLayout;/** * Created by fuweiwei on 2016/1/20. */public class MyLinearLayout extends LinearLayout {    private static final  String TAG="MyLinearLayout";    public MyLinearLayout(Context context) {        super(context);    }    public MyLinearLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.v(TAG, "onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int action = event.getAction();        switch (action)        {            case MotionEvent.ACTION_DOWN:                Log.v(TAG, "onTouchEvent;----> ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.v(TAG, "onTouchEvent;----> ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.v(TAG, "onTouchEvent;----> ACTION_UP");                break;            default:                break;        }        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.v(TAG, "dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {        Log.v(TAG, "requestDisallowInterceptTouchEvent");        super.requestDisallowInterceptTouchEvent(disallowIntercept);    }}

我们覆写了View和ViewGroup的所有Touch事件方法,然后我们分别设置监听事件:

package com.test.touch;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;public class MainActivity extends AppCompatActivity {    private MyButton mBtn;    private MyLinearLayout mLin;    private static final  String TAG="MyButton";    private static final  String TAG1="MyLinearLayout";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mLin = (MyLinearLayout) findViewById(R.id.lin);        mLin.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.v(TAG1, "OnClick");            }        });        mBtn = (MyButton) findViewById(R.id.btn);        mBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.v(TAG, "OnClick");            }        });       /* mBtn.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.v(TAG, "onTouch");                return false;            }        });        mBtn.setOnLongClickListener(new View.OnLongClickListener() {            @Override            public boolean onLongClick(View v) {                Log.v(TAG, "OnLong");                return false;            }        });*/    }}
当我们点击button按钮时,后台输出的log日志:

01-21 13:48:43.790 18642-18642/com.test.touch V/MyLinearLayout: dispatchTouchEvent:01-21 13:48:43.800 18642-18642/com.test.touch V/MyLinearLayout: onInterceptTouchEvent:01-21 13:48:43.800 18642-18642/com.test.touch V/MyButton: dispatchTouchEvent01-21 13:48:43.800 18642-18642/com.test.touch V/MyButton: onTouchEvent;----> ACTION_DOWN01-21 13:48:43.870 18642-18642/com.test.touch V/MyLinearLayout: dispatchTouchEvent:01-21 13:48:43.870 18642-18642/com.test.touch V/MyLinearLayout: onInterceptTouchEvent:01-21 13:48:43.870 18642-18642/com.test.touch V/MyButton: dispatchTouchEvent01-21 13:48:43.880 18642-18642/com.test.touch V/MyButton: onTouchEvent;----> ACTION_UP01-21 13:48:43.880 18642-18642/com.test.touch V/MyButton: OnClick

我们可以清楚的看到事件分发的流程:

MyLinearLayout的dispatchTouchEvent ->MyLinearLayout的onInterceptTouchEvent ->MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent

可以看出,在View上触发事件,最先捕获到事件的为View所在的ViewGroup,然后才会到View自身~,响应View的点击事件。而MyLinearLayout的点击事件没有响应,是因为Mybutton处理了这个点击事件。

当我们把MyButton中的dispatchTouchEvent()方法返回false时:

我们点击button时,log日志输出:

01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: dispatchTouchEvent01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: onInterceptTouchEvent01-21 13:56:39.050 6422-6422/com.test.touch V/MyButton: dispatchTouchEvent01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_DOWN01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: dispatchTouchEvent01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_UP01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: OnClick

这时没有执行MyButton的onTouchEvent 事件,没有我们没有吧时间传递给MyButton,所有由上层的View也就是MyLinearLayout执行onTouchEvent 响应点击事件。


当我们把MyLinearLayoutonInterceptTouchEvent 返回true时,拦截时间,效果也是一样的:

我们再点击button时,log日志输出:

01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: dispatchTouchEvent01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: onInterceptTouchEvent01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_DOWN01-21 14:03:14.730 15599-15599/com.test.touch V/MyLinearLayout: dispatchTouchEvent01-21 14:03:14.730 15599-15599/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_UP01-21 14:03:14.740 15599-15599/com.test.touch V/MyLinearLayout: OnClick
这时没有执行MyButton的任何Touch方法,因为被 MyLinearLayout拦截了,自己处理了。

通过案例是不是很清晰了,我们可以多多试试其他的操作,可以加深我们对事件分发直接的理解。下面有我测试的源码,代码很简单。这里向大家推荐下鸿洋大神的博客:http://blog.csdn.net/lmj623565791/article/details/39102591,他讲的很深,我也是学习了他的然后自己总结了,方便以后学习。


源码下载






更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. Android学习路线
  5. Android(安卓)开发中使用 SQLite 数据库
  6. android输入法弹出调整布局与沉浸式状态栏冲突+fitSystemWindows
  7. Android事件分发机制
  8. Android面试系列文章2018之Android部分ContentProvider篇
  9. android 事件分发机制(源码解析)

随机推荐

  1. 两种button点击后改变颜色的方法selectot
  2. Android中使EditText失去焦点禁止弹出键
  3. [置顶] android 抽屉效果,内容GridView来
  4. android点击AlertDialog的button不退出对
  5. Android里webviewActivity一般功能实现
  6. android 中文 API (41) —— RatingBar.O
  7. fir.im Weekly - 600 个 Android(安卓)开
  8. Android Things APP版本更新解决方案
  9. 81.s1-禁用checkBox点击事件
  10. Android 四大组件之 Service(一)