前言
在智能手机普及之前,人们依靠键盘与手机交互,可是实现的功能很有限,使用起来也很不方便。智能手机出现之后,人与手机的交互变得多样,只需要点一点,滑一滑就能实现很多功能。尤其现在手机的发展的趋势是连仅存虚拟键盘都要去除,完全凭借手势去操控手机,比如现在很多手机向左滑就是退出页面,现在想像一个场景,就是你看小说翻页也需要向左滑,如果你向左滑就退出了,这肯定不是你想要的,那么怎么实现在看小说的时候不退出呢?这就引出了我们今天的主题——事件分发,希望你看完这篇文章就会得到你心中的答案。
目录
事件的分类
事件是事件分发的对象,也就是与屏幕的交互,事件有以下四种
注:就一般而言,move事件在一次滑动中会多次触发,比如在B点按下,滑动到C点,然后离开,在滑动期间会发生多次而不是一次move事件。
相关函数
在讨论相关函数之前,我们先看一下事件是在谁之间传递的。一般App的结构可能如下图所示,一个Activity,Activity中含有ViewGroup,ViewGroup中包含着View。事件就是在Activity,ViewGroup和View之间传递。
相关函数如下图
其中dispatchTouchEvent是当事件传入的时候调用,用于分发事件。OnTouchEvent处理事件,OnInterceptTouchEvent是ViewGroup特有事件,是否拦截事件,如果拦截事件则事件不会接着流向view。
事件分发的过程
仅仅通过上面三个函数不能很直观的了解这个过程,接下来我们用一个例子来说明这个过程。例子也很简单,一个Activity,里面有一个LinearLayout作为ViewGroup,ViewGroup里面是一个WebView,布局文件如下,因为要重写三个函数,所以LinearLayout 和WebView都重写了就是其中的LL和TWebView。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<io.appetizer.touch.LL
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<io.appetizer.touch.TWebView
android:layout_width="match_parent"
android:layout_height="match_parent">
</io.appetizer.touch.TWebView>
</io.appetizer.touch.LL>
</androidx.constraintlayout.widget.ConstraintLayout>
在什么都不处理的情况下,事件传递的情况如下
从这张图我们可以发现两件事:
(1)事件的传递顺序是 Activity -> ViewGroup -> View,在没有拦截的情况下事件处理的顺序是 View -> ViewGroup -> Activity。
(2)如我们把从ACTION_DOWN,ACTION_MOVE, ACTION_UP看作一个整体,当ACTION_DOWN传递下去,结果没有响应,没有被消费,又返回到Activity的onTouchEvent,则后续事件不会再向下传递。
实验一
注意:三个函数的返回值都是布尔值
当我们让ViewGroup的OnInterceptTouchEvent返回false,结果如下
当我们让ViewGroup的OnInterceptTouchEvent返回true,结果如下
可以看到当OnInterceptTouchEvent返回true时,ViewGroup会拦截事件,事件不会再流向View。
实验二
从实验一我们可以看出ViewGroup拦截了事件,但是ViewGroup并没有消费事件,事件最后还是流回了Activity,那么我们如何在ViewGroup消费事件,并且不想Activity对事件作出反应,否则可能会导致滑动冲突,就如我们开篇所讲的翻页和退出App之间的冲突,如果事件流回了Activity,那么可能直接退出了,这不是我们所想要的。为了消费事件,一般要对控件添加setOnTouchListener的监听,并且返回true,表示事件就此消费,不再往回传递,代码如下:
LinearLayout ll = findViewById(R.id.ll);
ll.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TEST", "ViewGroup 消费事件" );
return true;
}
});
执行结果如下:
从图中我们可以看到事件在ViewGroup被消费,并且没有再回传给Activity。
实验三
实验二我们看到了GroupView如何拦截和消费事件,那么View又是如何消费事件的呢,大部份的流程是一样的,都需要设置setOnTouchListener,返回true,还需要加上一个函数requestDisallowInterceptTouchEvent(boolean disallowIntercept)请求父控件不要拦截事件,参数为ture,则不会拦截,代码如下
TWebView tWebView = findViewById(R.id.tWebView);
tWebView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
v.getParent().requestDisallowInterceptTouchEvent(true);
Log.d("TEST TOUCH", "View 消费事件");
return true;
}
});
执行结果如下
如图所示,事件被View消费且没有回传。
最后
如有错误,欢迎指正,如果觉得写得还行,点赞就是最大的支持。更多资料和文章可以关注微信公众号QStack,追寻最纯粹的技术,享受编程的快乐。