Android触摸事件解析(自定义DrawerLayout)
基础知识概述:
首先来了解三个方法:
- dispatchTouchEvent(MotionEvent ev) 功能:事件的分发
- onInterceptTouchEvent(MotionEvent ev) 功能:事件的拦截
- onTouchEvent(MotionEvent ev) 功能:事件的消费
可以看到这三个方法里面都包含了同一个类:MotionEvent 类,这个类里面包含了很多的代表触摸动作的常量,比如:- MotionEvent.ACTION_DOWN: 代表手指按下的动作
- MotionEvent.ACTION_MOVE: 代表手指滑动的动作
- MotionEvent.ACTION_CANCEL: 代表手指动作取消的动作
- MotionEvent.ACTION_UP: 代表手指动作抬起的动作
这里我们也是围绕着这四个动作进行解析,因为:** 从手指触摸屏幕,到手指离开屏 幕,会经历一次 ACTION_DOWN、若干次ACTION_MOVE或ACTION_CANCEL、和一次ACTION_UP,这也被称为一个事件序列。**
之后我们在说一下能够触发这三个方法传递触摸事件的容器:
- Activity 事件就是从它开始往下分发的,这个容器里包含了dispatchTouchEvent()和onTouchEvent(),也就是拥有分发事件和消费事件的能力。
- ViewGroup 它接收Activity的事件分发,并且可以选择是否继续往下传递还是自己直接进行消费,所以它包含了上面三个方法,具有事件的分发、拦截、消费功能。
- View 如果它的父容器不进行拦截的话,它接受父容器的事件分发,并且判断是否消费事件,并且要把结果再通过dispatchTouchEvent()方法返回给父容器。
用表格的形式展现下容器和方法之间的关系:
事件相关方法 | 方法功能 | Activity | ViewGroup | View
---||||---|||---
public boolean dispatchTouchEvent | 事件分发 | Yes | Yes | Yes
public boolean onInterceptTouchEvent | 事件拦截 | No | Yes | No
public boolean onTouchEvent | 事件消费 | Yes | Yes | Yes
- 分发 : dispatchTouchEvent如果返回true,则表示在当前View或者其子View(子子...View)中,找到了处理事件的View;反之,则表示没有寻找到。
- 拦截: onInterceptTouchEvent如果返回true,则表示这个事件由当前View进行处理,不管处理结果如何,都不会再向子View传递这个事件;反之,则表示当前View不主动处理这个事件,除非他的子View返回的事件分发结果为false。
- 消费: onTouchEvent如果返回true,则表示当前View消费了该事件;反之,则表示当前View没有消费该事件,返回到父控件处理(如果有父控件的话)
代码示例
需求:我想用DrawerLayou类来做滑动侧边看,但是有一个问题,就是当侧边栏打开的时候,主屏幕会变暗并且不能触发点击事件,我现在想通过自定义的形式解决这一问题,并且当侧边栏打开的时候点击主界面的时候侧边栏不会自定收起。
先看下效果图:
图片.png
再看下布局的资源文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:titleTextColor="@color/colorAccent" />
<com.mllwf.slidesidebar.TEDrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mllwf.slidesidebar.CustormFrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mllwf.slidesidebar.CustormButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:onClick="toCanSlieDrawer"
android:padding="16dp"
android:text="是否能滑动" />
</com.mllwf.slidesidebar.CustormFrameLayout>
<RelativeLayout
android:id="@+id/nav_view"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/darker_gray">
<Button
android:id="@+id/btn_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="这是顶部按钮"
android:textColor="@color/colorAccent" />
<Button
android:id="@+id/btn_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@color/colorPrimary"
android:text="这是中间的按钮"
android:textColor="@color/colorAccent" />
<Button
android:id="@+id/btn_three"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/colorPrimary"
android:text="这是底部按钮"
android:textColor="@color/colorAccent" />
</RelativeLayout>
</com.mllwf.slidesidebar.TEDrawerLayout>
</LinearLayout>
简要说明(请结合效果图和布局文件):
- 为了能让侧边栏在标题栏的下面,我在DrawerLayout布局放在了ToolBar布局的下面,而根布局我用的是线性布局。
- 可以看的我自定义了DrawerLayout类,这样做为了重写上面三个触摸事件分发,并且方便打印Log日志
- DrawerLayout容器里面的第一个布局代表的是主界面布局,这里我用的是自定义的FrameLayout这样是也是为了重写事件分发和打印日志(注意重写方法为了实现上面说的功能,和打印Log)
- 在主界面布局里面我放了一个自定义的按钮这只是为了方便打印日志用。
- DrawerLayout容器里面第二个布局代表的是侧边栏布局。
下面分别看下分别看些这几个自定义布局的相关代码:
-
MainActivity.class(Activity)
Activity触摸事件方法.png -
CustormFrameLayout(ViewGroup)
ViewGroup触摸事件.png -
CustormButton(View)
View事件触摸方法.png
首先默认没有打开侧边栏,点击按钮查看日志信息:
图片.png
从上面的日志信息可以看到,是一层层的传递的,这里用一张图解释下上面的传递流程:
图片.png
至于点击主界面不让侧边栏收回,就让主界面在动作为Activity_Up的时候消费事件(onTouchEvent()返回true)就可以了(因为DrawerLayout类在onTouchEvent()方法里关闭了侧边栏)
ViewGroup#onTouchEvent().png DrawerLayout#onTouchEvent().png
最后在总结一下:
- 一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕结束,这个过程中产生的一系列事件。一个事件序列以ACTION_DOWN事件开始,中间可能经过若干个MOVE,以ACTION_UP事件结束。
- 事件的传递过程是由外向内的,即事件总是由父元素分发给子元素
- 如果某个View消费了ACTION_DOWN事件,那么通常情况下,这个事件序列中的后续事件也将交由其进行处理,但可以通过调用其父View的onInterceptTouchEvent方法,对后续事件进行拦截
- 如果某个View它不消耗ACTION_DOWN事件,那么这个序列的后续事件也不会再交由它来处理
- 如果事件没有View对其进行处理,那么最后将有Activity进行处理
- 如果事件传递的结果为true,回传的结果直接通过不断调用父View#dispatchTouchEvent方法,传递给Activity;如果事件传递的结果为false,回传的结果不断调用父View#onTouchEvent方法,获取返回结果。
- View默认的onTouchEvent在View可点击的情况下,将会消耗事件,返回true;不可点击的情况下,则不消耗事件,返回false(longClickable的情况,读者可以自行测试,结果与clickable相同)
- 如果某个ViewGroup的onInterceptTouchEvent返回为true,那么这个事件序列中的后续事件,不会在进行onInterceptTouchEvent的判断,而是由它的dispatchTouchEvent方法直接传递给onTouchEvent方法进行处理
- 如果某个View接收了ACTION_DOWN之后,这个序列的后续事件中,在某一刻被父View拦截了,则这个字View会收到一个ACTION_CANCEL事件,并且也不会再收到这个事件序列中的后续事件
项目源码
参考文章
有问题欢迎联系我(1021423736@qq.com)