Anroid事件分发
因为最近因个人原因离职,面试的时候,有人问到了Android中事件分发机制的过程,因为忘得差不多了,没答好,所以回来后,想写了个Demo,重新复习一遍。
一般来说,Android的组件其实可以分为两类,即View和ViewGroup,而ViewGroup又继承于View。对于ViewGroup的是事件分发主要涉及到三个方法:
dispatchTouchEvent(MotionEvent ev),onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event),而View主要涉及到
dispatchTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)。顾名思义,dispatchTouchEvent用于分发Touch事件,
onInterceptTouchEvent用于拦截Touch事件,而onTouchEvent则是用于处理Touc事件。
首先,我们写一个简单的Demo来看看,
public class MyLinearLayout extends LinearLayout { public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyLinearLayout(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("test", "MyLinearLayout"+" dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("test", "MyLinearLayout"+" onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("test", "MyLinearLayout"+" onTouchEvent"); return super.onTouchEvent(event); } }
public class MyView extends TextView { public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } public MyView(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d("test", "MyView"+" dispatchTouchEvent"); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("test", "MyView"+" onTouchEvent"); return super.onTouchEvent(event); } }
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.eventdemo.MainActivity" > <com.example.test1.MyLinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <com.example.test1.MyView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="00000000000000000000" /> </com.example.test1.MyLinearLayout> </RelativeLayout>
上面我主要定义了一个ViewGroup(MyLinearLayout)和一个View(MyView),然后分别打印出dispatchTouchEvent(MotionEvent ev),onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)的Log:
通过上面的Log可以看出,ViewGroup和View中的事件默认分发顺序是:ViewGruop.dispatchTouchEvent --> ViewGruop.onInterceptTouchEvent
--> View.dispatchTouchEvent --> View.onTouchEvent --> ViewGroup.onTouchEvent.
那如果我们对View的onTouchEvent事件的返回值修改下,会变成什么样呢?
首先,我将MyView中的onTouchEvent方法中直接return true,然后看下Log:
可以发现,MyLinearLayout的onTouchEvent没有执行,这是因为这次Touch事件已经被MyView消费了,即如果我们在方法中直接return true,那么事件就不会继续分发下去了。
接着,我们在MyLinearLayout的dispatchTouchEvent方法中return true,然后看Log:
总结如下:
- dispatchTouchEvent返回true,意味后续不会将事件进行分发,而是直接在dispatchTouchEvent中进行拦截,即后续的ACTION_MOVE,ACTION_UP,ACTION_CANCEL事件只会触发ViewGroup和ViewGroup之前路径的View以及ViewGroup(需要注意的是,如果是在onTouch中,返回true,则下次的ACTION_DOWN,ACTION_UP就只会触发该ViewGroup的onTouch事件,即系统是会记住消费了onTouch事件的那个ViewGroup来传递事件)的dispatchTouchEvent和onInterceptTouchEvent
,而不会触发ViewGroup以及ViewGroup之后的View的onInterceptTouchEvent()和onTouchEvent()事件. - dispatchTouchEvent返回返回false,意味着不处理该事件,也不往下分发事件。即后续不会收到ACTION_MOVE,ACTION_UP,ACTION_CANCEL事件.
- 返回super.dispatchTouchEvent(ev)意味着不处理该事件,而是依次向子View分发该事件,直到找到需要消费本次事件的View.
所以如果我们需要将事件拦截下来的话,直接return true就可以了。