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事件方法:
dispatchTouchEvent(MotionEvent ev):事件分发,以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
onInterceptTouchEvent(MotionEvent ev):事件拦截,只有ViewGroup有这个方法,View是没用的,因为View是最后一层,不用拦截。onInterceptTouchEvent的处理逻辑:
onTouchEvent(MotionEvent ev): 事件响应,ViewGroup在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或View在dispatchTouchEvent 返回super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
如果返回了 true 则会接收并消费该事件。
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); } }
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); } }
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: dispatchTouchEvent 01-21 13:48:43.800 18642-18642/com.test.touch V/MyButton: onTouchEvent;----> ACTION_DOWN 01-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: dispatchTouchEvent 01-21 13:48:43.880 18642-18642/com.test.touch V/MyButton: onTouchEvent;----> ACTION_UP 01-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: dispatchTouchEvent 01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: onInterceptTouchEvent 01-21 13:56:39.050 6422-6422/com.test.touch V/MyButton: dispatchTouchEvent 01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_DOWN 01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_UP 01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: OnClick
这时没有执行MyButton的onTouchEvent 事件,没有我们没有吧时间传递给MyButton,所有由上层的View也就是MyLinearLayout执行onTouchEvent 响应点击事件。
当我们把MyLinearLayout中onInterceptTouchEvent 返回true时,拦截时间,效果也是一样的:
我们再点击button时,log日志输出:
01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: onInterceptTouchEvent 01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_DOWN 01-21 14:03:14.730 15599-15599/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 14:03:14.730 15599-15599/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_UP 01-21 14:03:14.740 15599-15599/com.test.touch V/MyLinearLayout: OnClick这时没有执行MyButton的任何Touch方法,因为被 MyLinearLayout拦截了,自己处理了。
通过案例是不是很清晰了,我们可以多多试试其他的操作,可以加深我们对事件分发直接的理解。下面有我测试的源码,代码很简单。这里向大家推荐下鸿洋大神的博客:http://blog.csdn.net/lmj623565791/article/details/39102591,他讲的很深,我也是学习了他的然后自己总结了,方便以后学习。