Android中事件分发机制
来源:互联网 发布:淘宝宝贝发货地址设置 编辑:程序博客网 时间:2024/05/15 00:53
在 Android 的诸多点击事件中,似乎存在很多问题,为什么图片轮播器里的图片使用ImageView无法监听?为什么按钮和布局同时监听会相互干扰?类似一系列的问题本质上就是 Android 中事件分发机制的问题,只有理解事件分发机制,我们才能对事件响应作出正确的判断。
概述
我们通过一个例子来看看事件分发机制的过程,我们有这样一个简单的界面,一个 Activity 中有一个全屏的MyLayout(继承自Linearlayout),在MyLayout中间有一个按钮 MyButton (继承自 Button),上面写着CLICK ME
代码如下:
<?xml version="1.0" encoding="utf-8"?><com.example.hkxlegend.shijianfenfa.app.MyLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <com.example.hkxlegend.shijianfenfa.app.MyButton android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="click me" /></com.example.hkxlegend.shijianfenfa.app.MyLayout>
然后我们看一下Activity、自定义ViewGroup和MyButton的代码
主Activity的代码:
public class MainActivity extends AppCompatActivity { private static final String TAG = "tag"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onTouchEvent(MotionEvent event) { Log.v(TAG, "activity-touchEvent" + event.getAction()); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.v(TAG, "activity-dispatchTouchEvent" + ev.getAction()); return super.dispatchTouchEvent(ev); }}
MyLayout的代码:
/** * @since 2016 */public class MyLayout extends LinearLayout { private static final String TAG = "tag"; public MyLayout(Context context) { super(context); } public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent event) { Log.v(TAG,"layout-touchEvent"+event.getAction()); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.v(TAG,"layout-dispatchTouchEvent"+ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.v(TAG,"layout-interceptTouchEvent"+ev.getAction()); return super.onInterceptTouchEvent(ev); }}
MyButton的代码:
/** * @since 2016 */public class MyButton extends Button { private static final String TAG = "tag"; 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 dispatchTouchEvent(MotionEvent event) { Log.v(TAG, "button-dispatchTouchEvent" + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.v(TAG, "button=touchEvent" + event.getAction()); return super.onTouchEvent(event); }}
然后我们点击一下界面上的按钮,通过Log日志,我们可以看到如下输出
在Android中,Touch事件都是从ACTION_DOWN开始的,一次完整的触摸事件中,Down和Up都只有一个,Move有若干个。触摸事件的传递过程主要涉及三个Touch事件:dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent。从上面的Log日志我们可以看出来,Event0代表着Down事件,Event1代表着Up事件,Event2代表着Move事件。
dispatchTouchEvent:这个方法用来分发TouchEvent(Activity/ViewGroup/View)
onInterceptTouchEvent:这个方法用来拦截TouchEvent,默认返回false,返回true表示拦截(ViewGroup)
onTouchEvent:这个方法用来处理TouchEvent(Activity/ViewGroup/View)
下面我们就从这三个事件开始说起
Touch 事件
Android中默认情况下事件传递是由最终的view的接收到,传递过程是从父布局到子布局,也就是从Activity到ViewGroup到View的过程,默认情况,ViewGroup起到的是透传作用。总的事件流程我们可以根据这个图看出来
dispatchTouchEvent事件
dispatchTouchEvent 会将一个Touch事件进行分发,事件分发时从根节点的ViewGroup分发开始,ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViewGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法来向下分发
ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,根据源码我们可以看到
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
在View中,首先有一个判断,这三个条件分别是:是否给控件注册了touch事件 & 判断当前点击的控件是否是enable的(button默认是enable) & 回调控件注册touch事件时的onTouch方法,也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立;
满足以上三个条件,整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。
关注返回值:
- 默认:默认继续传递事件
- true:事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递
- false:会将事件返回给父 View 的 onTouchEvent 进行消费
如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费
如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费
在项目开始阶段,每个返回值都是默认super形式的,这样的形式会形成一个完整的事件传递(如上图),现在我们通过改动返回值测试一下其他情况,测试中全部用Event0事件举例
1. 将Activity中dispatchTouchEvent的值改为true和false
- 当返回值为true时
V/tag: activity-dispatchTouchEvent0
返回为true时候,事件分发停止,由本Activity的dispatchTouchEvent方法处理事件
- 当返回值为false时
V/tag: activity-dispatchTouchEvent0
交由父View的onTouchEvent事件,由于没有父View,没有后续执行步骤
2. 将MyLayout中dispatchTouchEvent的值改为true和false
- 当返回值为true时
V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
返回为true时候,事件分发停止,由本MyLayout的dispatchTouchEvent方法处理事件
- 当返回值为false时
V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: activity-touchEvent0
交由父View即Activity的onTouchEvent事件执行
3. 将MyButton中dispatchTouchEvent的值改为true和false
当返回值为true时
V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
事件被MyButton的dispatchTouchEvent本身消费
当返回值为false时
V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
V/tag: layout-touchEvent0
分发给父View的onTouchEvent方法进行了消费
通过以上的测试,我们就可以知道dispatchTouchEvent的事件分发是什么形式,总结起来可以归类为一张图,图上标注的都是dispatchTouchEvent事件的返回值
onInterceptTouchEvent事件
onInterceptTouchEvent这个方法的返回值是最简单的,表示是否拦截事件,这个只有在ViewGroup组件中存在,所以ViewGroup可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,事件交由本ViewGroup的onTouchEvent事件处理;返回false代表不对事件进行拦截,默认返回false。
onTouchEvent事件
onTouchEvent 的默认返回值为true ,表示对事件进行了处理
如果某个控件的onTouchEvent返回值为true,ACTION_DOWN以及后续的n个ACTION_MOVE与1个ACTION_UP都会逐层传递到这个控件的onTouchEvent进行处理。我们在MyBotton的onTouchEvent方法中返回true(默认值),可以看到Log日志:
V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
V/tag: button-touchEvent0
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-interceptTouchEvent2
V/tag: button-dispatchTouchEvent2
V/tag: button-touchEvent2
V/tag: activity-dispatchTouchEvent1
V/tag: layout-dispatchTouchEvent1
V/tag: layout-interceptTouchEvent1
V/tag: button-dispatchTouchEvent1
V/tag: button-touchEvent1
如果返回值是false,则会将ACTION_DOWN传递给其父ViewGroup的onTouchEvent进行处理,直到由哪一层ViewGroup消费了ACTION_DOWN事件为止,后续的n个ACTION_MOVE与1个ACTION_UP都会逐层传递到处理这个事件的那一层View或ViewGroup,我们现在将MyButton的onTouchEvent返回为false,MyLayout的onTouchEvent返回值为true看一下Log日志:
V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
V/tag: button-touchEvent0
V/tag: layout-touchEvent0
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-touchEvent2
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-touchEvent2
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-touchEvent2
V/tag: activity-dispatchTouchEvent1
V/tag: layout-dispatchTouchEvent1
V/tag: layout-touchEvent1
Touch 事件说完后,我们来看一下onClick事件,看一下onClick 和 Touch 有没有什么关系
onClick 事件
还是这一段代码,View的dispatchTouchEvent代码
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
可以看到,第三个判断比较重要,如果对一个控件添加onClick事件,当控件中onTouch方法返回是true的时候,onClick事件没有响应。 这是为什么呢?我们来看一下第三个判断
第三个判断回调控件注册touch事件时的onTouch方法,也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,无法执行下面的onTouchEvent方法,我们要知道onClick的调用是在onTouchEvent(event)方法中的performClick()方法,所以如果此时onTouch返回了true,onClick()方法就无法执行。
我们还要知道,performClick()这个方法是在onTouchEvent()方法的Up事件中的,所以执行流程也是先onTouch()然后才会执行onClick()事件。
通过这些,我们就大概了解了Android中事假的分发机制
- Android中事件分发机制
- android中事件分发机制
- Android中事件分发机制
- Android中事件分发机制
- android中view的事件分发机制
- Android中触摸事件传递分发机制
- Android中事件分发机制理解
- Android中事件分发机制详解
- Android中View的事件分发机制
- Android中View的事件分发机制
- Android中View的事件分发机制
- Android中View事件分发机制
- Android中View的事件分发机制
- Android中View的事件分发机制
- android中touch事件分发机制
- android中touch事件的分发机制
- Android中事件的分发机制
- Android中View的事件分发机制
- 判断服务器资源使用是否合理
- Java之BufferedInputStream详解 源码分析学习笔记
- 使用Java实现的简易“生产者消费者问题”
- nsenter工具进入docker容器
- 使用Spring Boot快速构建应用
- Android中事件分发机制
- 如何解决npm should be run outside of the node repl, in your normal shell问题
- html class
- Java中的类与对象
- 写给自己,梳理一下我现在对前端知识结构的理解
- 自动释放池详解
- 网络视频直播将给影视界带来重大影响
- Vuforia+Unity AR场景播放音频控制
- 托管代码与非托管代码之间与托管程序