Framework事件机制——手撕Android事件处理的三种方法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Framework事件机制——手撕Android事件处理的三种方法相关的知识,希望对你有一定的参考价值。
参考技术Aandroid的事件处理的三种方法:
setOnClickListener,setOnLongClickListener、setOnTouchListener
注意:如果onTouchEvent方法return true,则单击事件和长摁事件不再执行;若onLongClick方法返回true,则单击事件不再处理。
需要定义继承组件的类,重写回调方法Touch方法执行时,先被Activity捕获,DispatchTouchEvent方法处理。return false,交给上层的onTouchEvent方法处理;return super.dispatchTouchEvent(ev),则传递给最外层的View。
View用Dispatch方法处理,return false,由上层的onTouchEvent方法处理。如果返回super.dispatchTouchEvent(ev),则本层的onInterceptTouchEvent拦截,如果拦截true,则拦截,false不拦截,传递给子View的DispatchTouchEvent处理。
常用的回调方法:onKeyDown,onKeyLongPress,onKeyUp,onTouchEvent,onTrackballEvent(轨迹球事件)监听和回调同时存在时,先调用监听。
流程模型图:
Event source 事件源
Event 事件
Event Listener 事件监听器
下面我们来看一下点击事件和触摸事件的监听三要素具体是那部分:
由于点击事件比较简单,系统已经帮我们处理了,并没有找到具体事件是哪个。
View.OnClickListener 单击事件监听器必须实现的接⼝
View.OnCreateContextMenuListener 创建上下⽂菜单事件
View.OnFocusChangeListener 焦点改变事件
View.OnKeyListener 按键事件监听器
View.OnLongClickListener 长按事件监听器
View.OnTouchListener 触摸屏事件监听器
⾸先,事件监听机制中由事件源,事件,事件监听器三类对象组成。
事件监听器处理流程:
在此以OnClickListener单击事件为例使用intent来实现页面的跳转
监听事件处理是事件源与事件监听器分开的而基于回调的事件处理UI组件不但是事件源,而且还是事件监听器,通过组件的相关回调方法处理对应的事件。
Ⅰ. 自定义View类,继承自需要的View UI类。ex :自定义 MyButton按钮类 extends 基础Button类
Ⅱ. 复写回调函数。ex:public boolean onTouchEvent(MotionEvent event)
每一个事件回调方法都会返回一个boolean值,①.如果返回true:表示该事件已被处理,不再继续向外扩散,②.如果返回false:表示事件继续向外扩散
而说到基于回调就离不开监听机制 。
几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于表示该处理方法是否能完全处理该事件。
如果处理事件的回调方法返回true,表明该处理方法已经完全处理改事件,该事件不会传播出去。
如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去。
对于基于回调的时间传播而言,某组件上所发生的事件不仅会激发该组件上的回调方法,也会触发该组件所在Activity的回调方法——只要事件能传播到该Activity。
这里是在模拟器里进行的测试,这里按下键盘(而不是点击),会看到 logcat 中的输出,如下:
View类实现了KeyEvent.Callback接口中的一系列回调函数,因此,基于回调的事件处理机制通过自定义View来实现,自定义View时重写这些事件处理方法即可。
Handler是一个消息分发对象。
Handler是Android系统提供的一套用来更新UI的机制,也是一套消息处理机制,可以通过Handler发消息,也可以通过Handler处理消息。
在下面介绍Handler机制前,首先得了解以下几个概念:
在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage ,向消息队列中添加消息。 当通过 Looper.loop 开启循环后,会不断地从消息池中读取消息,即调用 MessageQueue.next , 然后调用目标Handler(即发送该消息的Handler)的 dispatchMessage 方法传递消息, 然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage 方法,接收消息,处理消息。
从上面可以看出,在子线程中创建Handler之前,要调用 Looper.prepare() 方法,Handler创建后,还要调用 Looper.loop() 方法。而前面我们在主线程创建Handler却不要这两个步骤,因为系统帮我们做了。
初始化Looper :
从上可以看出,不能重复创建Looper,每个线程只能创建一个。创建Looper,并保存在 ThreadLocal 。其中ThreadLocal是线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。
开启Looper
发送消息 :
post方法:
send方法:
在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新UI。
本文讲解了三个方面;Android事件机制;基于监听、基于回调以及Handler消息处理。还有许多没有讲解到的知识点,我总结在了整理的一套Android进阶笔记里面;需要学习进阶的同学可以前往获取: Frame Work源码解析手册 、 Android核心技术进阶手册、实战笔记、面试题纲资料
Framework源码面试六部曲:4.事件传递机制
前言
今天在电脑上翻出了很久之前整理笔记Framework
源码面试,Flutter
,以及一部分面试专题。拿出来温习一下。
今天先讲Framework
源码篇:
1.
Framework
源码面试:1.Activity
启动流程
2.Framework
源码面试:2.Binder
面试
3.Framework
源码面试:3.Handler
面试
4.Framework
源码面试:4.事件分发机制
5.Framework
源码面试:5.onMeasure
测量原理
6.Framework
源码面试:6.Android
屏幕刷新机制
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。
废话不多说,总结一句:面试时事件分发机制很重要。
1.1 Android 事件原理和 事件分发流
先弄清楚Android 事件原理后 我们再来看怎么面试
关于Android 事件分发机制网上的博文很多,但是很多都是写个Demo然后贴一下输出的Log或者拿源码分析,然后一堆的注释和说明,如果用心的去看肯定是收获不少但是确实很难把整个流程说清和记住。曾经也是拼命想记住整个流程,但是一段时间又忘了,最后觉得分析这种问题和事件流的走向,一张图来解释和说明会清晰很多,下面我根据画的一张事件分发流程图,说明的事件从用户点击之后,在不同函数不同返回值的情况的最终走向。
注:
仔细看的话,图分为3层,从上往下依次是
Activity
、ViewGroup
、View
事件从左上角那个白色箭头开始,由
Activity
的dispatchTouchEvent
做分发箭头的上面字代表方法返回值,(
return true
、return false
、return super.xxxxx()
,super
的意思是调用父类实现。
dispatchTouchEvent
和
onTouchEvent
的框里有个**【true---->消费】**的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。目前所有的图的事件是针对
ACTION_DOWN
的,对于ACTION_MOVE和ACTION_UP
我们最后做分析。之前图中的
Activity
的dispatchTouchEvent
有误(图已修复),只有return super.dispatchTouchEvent(ev)
才是往下走,返回true
或者false
事件就被消费了(终止传递)。
仔细看整个图,我们得出事件流 走向的几个结论(希望读者专心的看下图 1,多看几遍,脑子有比较清晰的概念。)
1、如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。
所以如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super
调用父类的默认实现,那么整个事件流向应该是从Activity---->ViewGroup—>View
从上往下调用dispatchTouchEvent
方法,一直到叶子节点(View)的时候,再由View—>ViewGroup—>Activity
从下往上调用onTouchEvent
方法。
2、dispatchTouchEvent
和 onTouchEvent
一旦return true
,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。看下图中只要return true
事件就没再继续传下去了,对于return true
我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。
3、dispatchTouchEvent
和 onTouchEvent return false
的时候事件都回传给父控件的onTouchEvent
处理。
看上图深蓝色的线,对于返回false
的情况,事件都是传给父控件onTouchEvent
处理。
- 对于
dispatchTouchEvent
返回false
的含义应该是:事件停止往子View
传递和分发同时开始往父控件回溯(父控件的onTouchEvent
开始从下往上回传直到某个onTouchEvent return true
),事件分发机制就像递归,return false
的意义就是递归停止然后开始回溯。
- 对于
onTouchEvent return false
就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。
4、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent ViewGroup
和View
的这些方法的默认实现就是会让整个事件安装U型完整走完,所以 return super.xxxxxx()
就会让事件依照U型的方向的完整走完整个事件流动路径),中间不做任何改动,不回溯、不终止,每个环节都走到。
所以如果看到方法return super.xxxxx()
那么事件的下一个流向就是走U型下一个目标,稍微记住上面这张图,你就能很快判断出下一个走向是哪个控件的哪个函数。
5、onInterceptTouchEvent
的作用
Intercept
的意思就拦截,每个ViewGroup
每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)如果要自己处理那就在onInterceptTouchEvent
方法中 return true
就会交给自己的onTouchEvent
的处理,如果不拦截就是继续往子控件往下传。**默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent
拦截器return super.onInterceptTouchEvent()
和return false
是一样的,是不会拦截的,事件会继续往子View
的dispatchTouchEvent
传递。
6、ViewGroup
和View
的dispatchTouchEvent
方法返回super.dispatchTouchEvent()
的时候事件流走向。
首先看下ViewGroup
的dispatchTouchEvent
,之前说的return true
是终结传递。return false
是回溯到父View的onTouchEvent
,
然后ViewGroup
怎样通过dispatchTouchEvent
方法能把事件分发到自己的onTouchEvent
处理呢,return true
和false
都不行,那么只能通过Interceptor
把事件拦截下来给自己的onTouchEvent
,所以ViewGroup dispatchTouchEvent
方法的super
默认实现就是去调用onInterceptTouchEvent
,
记住这一点。
那么对于View
的dispatchTouchEvent return super.dispatchTouchEvent()
的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true
是终结。return false
是回溯会父类的onTouchEvent
,怎样把事件分发给自己的onTouchEvent
处理呢,那只能return super.dispatchTouchEvent
,View类的dispatchTouchEvent()
方法默认实现就是能帮你调用View自己的onTouchEvent
方法的
1.2 如何面试,以及面试官会出现什么考点
1.2.1、为什么会有事件分发机制?
我们知道,android的布局结构是树形结构,这就会导致一些View可能会重叠在一起,当我们手指点击的地方在很多个布局范围之内,也就是说此时有好多个布局可以响应我们的点击事件,这个时候该让哪个view来响应我们的点击事件呢?这就是事件分发机制存在的意义。
1.2.2、ViewGroup的事件分发涉及到哪些过程和方法?
public boolean dispatchTouchEvent(MotionEvent ev)
是事件分发机制中的核心,所有的事件调度都归它管
用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent
中调用,用来判断是否拦截某个事件,返回结果表示是否拦截当前事件public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent
中调用,用来处理点击事件,返回结果表示是否消耗当前事件
1.2.3、View中为什么会有dispatchTouchEvent方法,它存在的意义是什么?
我们知道View可以注册很多监听事件(下文有详细),比如,触摸事件,单击事件,长按事件等,而且view也有自己的onTouchEvent
方法,那么这么多事件应该由谁来调度管理呢?这就是是View中dispatchTouchEvent
方法存在的意义。
1.2.4、View中为什么没有onInterceptTouchEvent
事件拦截方法?
View最为事件传递的最末端,要么消费掉事件,要么不处理进行回传,根本没必要进行事件拦截
1.2.5、用伪代码表示ViewGroup的事件分发过程并解释?
public boolean dispatchTouchEvent(MotionEvent ev)
boolean consume = false;
if (onInterceptTouchEvent(ev))
consume = onTouchEvent(ev);
else
consume = child.dispatchTouchEvent(ev);
return consume;
- 对于一个
ViewGroup
来说,点击事件产生后,首先会传递给它,这时她的dispatchTouchEvent
会被调用,如果这个ViewGroup的onInterceptTouchEvent
- 方法返回true表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的
onTouchEvent
就会被调用;如果这个这个ViewGroup的onInterceptTouchEvent
- 方法返回false就表示它不拦截当前事件,这时事件就会传递给子元素,接着子元素的
dispatchTouchEvent
方法就会被调用,如此反复直到事件最终被处理。
1.2.6、简述事件传递的流程
- 事件都是从
Activity.dispatchTouchEvent()
开始传递 - 一个事件发生后,首先传递给Activity,然后一层一层往下传,从上往下调用
dispatchTouchEvent
方法传递事件:
activity --> ~~ --> ViewGroup --> View
- 如果事件传递给最下层的View还没有被消费,就会按照反方向回传给Activity,从下往上调用
onTouchEvent
方法,最后会到Activity的onTouchEvent()
函数,如果Activity也没有消费处理事件,这个事件就会被抛弃:
View --> ViewGroup --> ~~ --> Activity
dispatchTouchEvent
方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发,事件没有被消费。返回false则继续往下分发,如果是ViewGroup则分发给onInterceptTouchEvent
进行判断是否拦截该事件。onTouchEvent
方法用于事件的处理,返回true表示消费处理当前事件,返回false则不处理,交给子控件进行继续分发。onInterceptTouchEvent
是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent
进行处理。返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能还有子View,而在Android中View中是不能再包含子View的- 上层View既可以直接拦截该事件,自己处理,也可以先询问(分发给)子View,如果子View需要就交给子View处理,如果子View不需要还能继续交给上层View处理。既保证了事件的有序性,又非常的灵活。
- 事件由父View传递给子View,ViewGroup可以通过
onInterceptTouchEvent()
方法对事件拦截,停止其向子view传递 - 如果View没有对
ACTION_DOWN
进行消费,之后的其他事件不会传递过来,也就是说ACTION_DOWN
必须返回true,之后的事件才会传递进来
1.2.7、ViewGroup 和 View 同时注册了事件监听器(onClick等),哪个会执行?
事件优先给View,会被View消费掉,ViewGroup 不会响应。
1.2.8、当俩个或多个View重叠时,事件该如何分配?
当 View 重叠时,一般会分配给显示在最上面的 View,也就是后加载的View。
1.2.9、dispatchTouchEvent每次都会被调用吗?
是的,onInterceptTouchEvent
则不会。
1.2.10、一旦有事件传递给view,view的onTouchEvent一定会被调用吗?
View没有onInterceptTouchEvent
方法,一旦有事件传递给它,他的onTouchEvent
就一定会被调用。
1.2.11、ViewGroup 默认拦截事件吗?
ViewGroup默认不拦截任何事件;看源码可以知道ViewGroup的onInterceptTouchEvent
方法中只有一行代码:return false;
1.2.12、事件分为几个步骤?
down事件开头,up事件结尾,中间可能会有数目不定的move事件。
1.3 View事件的优先级会考哪些内容
1.3.1、基于监听的事件分发有哪些?怎么来设置监听?
我们常用的setOnClickListener
、OnLongClickListener
、setOnTouchListener
等都是基于监听的事件处理。
设置监听可以用如下几种方式:
-
匿名内部类:
view.setOnClickListener(new View.OnClickListener() @Override public void onClick(View v) );
-
内部类:
view.setOnClickListener(new MyClickListener()); class MyClickListener implements View.OnClickListener @Override public void onClick(View v)
-
外部类:
view.setOnClickListener(new MyClickListener()); public class MyClickListener implements View.OnClickListener @Override public void onClick(View v)
-
Activity实现
OnClickLister
接口的方式public class TestViewActivity extends AppCompatActivity implements View.OnClickListener MyView view; @Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_view); view = (MyView) findViewById(R.id.view); view.setOnClickListener(this); @Override public void onClick(View v)
-
在xml中绑定的方式:
public class TestViewActivity extends AppCompatActivity MyView view; @Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_view); view = (MyView) findViewById(R.id.view); public void MyClick(View view) <com.art.chapter_3.MyView android:id="@+id/view" android:layout_width="100dip" android:layout_height="100dip" android:background="@color/colorPrimaryDark" android:onClick="MyClick"/>
1.3.2、view的onTouchEvent,OnClickListerner和OnTouchListener的onTouch方法 三者优先级如何?
代码验证:
自定义view:
public class MyView extends View
@Override
public boolean onTouchEvent(MotionEvent event)
Log.i("--------","MyView onTouchEvent "+MyAction.getActionType(event));
return super.onTouchEvent(event);
监听:
yelloe.setOnTouchListener(new View.OnTouchListener()
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
Log.i("--------", "touch yelloe " + MyAction.getActionType(motionEvent));
return false;
);
yelloe.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Log.i("--------", "click yelloe ");
);
输出结果: 插图 优先级高低
优先级高低:
onTouchListener >>> onTouchEvent >>> setOnLongClickListener >>> OnClickListerner
1.3.3、如图有三個嵌套的控件,结构如下,其中黄色部分是一个继承于View的控件,绿色和红色都是继承于LinearLayout的控件: 插图:
代码简单如下:
public class MyView extends View
@Override
public boolean onTouchEvent(MotionEvent event)
Log.i("--------","MyView onTouchEvent "+MyAction.getActionType(event));
return super.onTouchEvent(event);
public class MyLinearLayoutRed extends LinearLayout
@Override
public boolean onTouchEvent(MotionEvent event)
Log.i("--------","MyLinearLayoutRed onTouchEvent "+MyAction.getActionType(event));
return super.onTouchEvent(event);
public class MyLinearLayoutGreen extends LinearLayout
@Override
public boolean onTouchEvent(MotionEvent event)
Log.i("--------","MyLinearLayoutRed onTouchEvent "+MyAction.getActionType(event));
return super.onTouchEvent(event);
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<com.example.administrator.myviewevent.MyLinearLayoutRed
android:id="@+id/red"
android:layout_width="300dip"
android:layout_height="300dip"
android:background="@color/red">
<com.example.administrator.myviewevent.MyLinearLayoutGreen
android:id="@+id/green"
android:layout_width="200dip"
android:layout_height="200dip"
android:background="@color/green">
<com.example.administrator.myviewevent.MyView
android:id="@+id/yellow"
android:layout_width="130dip"
android:layout_height="130dip"
android:background="@color/yellow" />
</com.example.administrator.myviewevent.MyLinearLayoutGreen>
</com.example.administrator.myviewevent.MyLinearLayoutRed>
</FrameLayout>
问题一:如果不在onTouchEvent
方法中做任何处理,只是Log输出每一层的Touch事件类型,现在用手指按下在黄色区域并移动后抬起.请问Log输出的结果是什么?
答:
- I/--------: MyView onTouchEvent ACTION_DOWN…
- I/--------: MyLinearLayoutGreen onTouchEvent ACTION_DOWN…
- I/--------: MyLinearLayoutRed onTouchEvent ACTION_DOWN…
问题二:如果不在onTouchEvent
方法和setOnTouchListener
的onTouch方法中做任何处理,只是Log输出每一层的Touch事件类型,现在用手指按下在黄色区域并移动后抬起.请问Log输出的结果是什么?
在Activity中增加setOnTouchListener
监听
yelloe.setOnTouchListener(new View.OnTouchListener()
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
Log.i("--------", "touch yelloe " + MyAction.getActionType(motionEvent));
return false;
);
green.setOnTouchListener(new View.OnTouchListener()
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
Log.i("--------", "touch green " + MyAction.getActionType(motionEvent));
return false;
);
red.setOnTouchListener(new View.OnTouchListener()
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
Log.i("--------", "touch red " + MyAction.getActionType(motionEvent));
return false;
);
答:
I/--------: touch yelloe ACTION_DOWN…
I/--------: MyView onTouchEvent ACTION_DOWN…
I/--------: touch green ACTION_DOWN…
I/--------: MyLinearLayoutGreen onTouchEvent ACTION_DOWN…
I/--------: touch red ACTION_DOWN…
I/--------: MyLinearLayoutRed onTouchEvent ACTION_DOWN…
1.3.4、setOnTouchListener中onTouch的返回值表示什么意思?
onTouch
方法返回true表示事件被消耗掉了,不会继续传递了,此时获取不到到OnClick
和onLongClick
事件;onTouch
方法返回false表示事件没有被消耗,可以继续传递,此时,可以获取到OnClick
和onLongClick
事件;
同理 onTouchEvent
和 setOnLongClickListener
方法中的返回值表示的意义一样;
1.3.5、setOnLongClickListener的onLongClick的返回值表示什么?
返回false,长按的话会同时执行onLongClick
和onClick
;如果setOnLongClickListener
返回true,表示事件被消耗,不会继续传递,只执行longClick
;
1.3.6、onTouch和onTouchEvent的异同?
onTouch
方法是View的OnTouchListener
接口中定义的方法。当一个View绑定了OnTouchLister
后,当有touch事件触发时,就会调用onTouch
方法。(当把手放到View上后,onTouch
方法被一遍一遍地被调用)onTouchEvent
方法是override
的Activity
的方法。重新了Activity
的onTouchEvent
方法后,当屏幕有touch
事件时,此方法就会被调用。onTouch
优先于onTouchEvent
执行,如果在onTouch
方法中通过返回true将事件消费掉,onTouchEvent
将不会再执行。- 相同点是它们都是在在View的dispatchTouchEvent中调用的;
1.3.7、点击事件的传递过程?
Activity-Window-View
。
从上到下依次传递,当然了如果你最低的那个view onTouchEvent
返回false
那就说明他不想处理 那就再往上抛,都不处理的话最终就还是让Activity
自己处理了。
1.3.8、如果某个view 处理事件的时候 没有消耗down事件 会有什么结果?
假如一个view,在down事件来的时候 他的onTouchEvent
返回false
, 那么这个down事件 所属的事件序列 就是他后续的move 和up 都不会给他处理了,全部都给他的父view处理。
1.3.9、如果view 不消耗move或者up事件 会有什么结果?
那这个事件所属的事件序列就消失了,父view也不会处理的,最终都给activity 去处理了。
1.3.10、enable是否影响view的onTouchEvent返回值?
不影响,只要clickable
和longClickable
有一个为真,那么onTouchEvent
就返回true。
以上是关于Framework事件机制——手撕Android事件处理的三种方法的主要内容,如果未能解决你的问题,请参考以下文章
Android 进阶——Framework 核心之Touch事件分发机制详细攻略
Android 进阶——Framework 核心之Touch事件分发机制详细攻略
“framework必会”系列:Android Input系统事件分发机制
Android 【手撕Glide】--Glide缓存机制(面试)