300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Android 事件分发机制分析及源码详解

Android 事件分发机制分析及源码详解

时间:2021-04-28 19:11:54

相关推荐

Android 事件分发机制分析及源码详解

Android 事件分发机制分析及源码详解

文章目录

Android 事件分发机制分析及源码详解事件的定义事件分发序列模型分发序列分发模型事件分发对象及相关方法源码分析事件分发总结

一般在实际开发中,我们很少主动去处理相关滑动处理,所以就很少关注事件分发相关机制。因为系统已经帮我们处理好了,如:ScrollView、ViewPage、ListView 等。

这里我们就以事件分发为入口,来分析一下事件分发机制与滑动冲突的解决方法。

学习 Android 开发过程中,或多或少都有听过事件分发机制,就算没听过,如果有面试经历,也大有几率被问到过吧。毕竟在现在,这个分发机制以及算是烂大街的题目了。

事件的定义

当用户触摸屏幕时,产生的一系列行为( Touch 事件)。

系统源码中通过MotionEvent这个类来描述这一系列相关行为。这个类中也定义了许多与事件相关的常量与变量,如事件类型、事件的相关属性等。这里就只贴出主要的四个相关类型

public final class MotionEvent extends InputEvent implements Parcelable {//按下事件:手指刚触碰屏幕public static final int ACTION_DOWN = 0;//松开事件:手指从屏幕松开public static final int ACTION_UP = 1;//移动事件:手指在屏幕上滑动public static final int ACTION_MOVE = 2;//取消事件:非人为因素取消public static final int ACTION_CANCEL = 3;}

事件分发序列模型

分发序列

正常情况下,一次手指触摸屏幕的行为会发出一系列点击事件,可以分为如下两种情况:

点击屏幕后立即松开:DOWN->UP点击屏幕滑动后再松开:DOWN->MOVE···->MOVE->UP

分发模型

我们在从宏观角度了解一下事件的分发总流程,总体来说就是:U型模型

事件先从Activity出发,然后传递给DecorView(ViewGroup),经过ViewGroup然后在传递给相应的子 View 进行处理。如果不处理,则按照原路径逐级向上返回,直至传递到Activity结束。

如下流程图表述应该比较容易理解。

事件分发对象及相关方法

通过上面的分发序列和分发模型,可以看出,一个事件的分发主要涉及三个对象:Activity、ViewGroup和View。

Activity:控制生命周期和处理事件ViewGroup:一组 View 的集合(可以包含多个子 View)View:所有 UI 组件的基类

而这三个分发对象都有相应的事件处理方法

dispatchTouchEvent(MotionEvent event)

用来进行事件分发onInterceptTouchEvent(MotionEvent ev)

判断是否拦截事件(值存在于 ViewGroup 中)onTouchEvent(MotionEvent event)

处理相关事件

源码分析

到这里我们对整个大致流程已经有了相关的印象了,接下来我们在从源码的角度来看看,事件是怎么传递的。代码省略了不重要的部分,并且加上了相关注释,就不过多进行解释了。

事件从ActivitydispatchTouchEvent方法开始向下分发进行处理。

public class Activity{public boolean dispatchTouchEvent(MotionEvent ev) {...//调用 window 实现类 PhoneWindow 的 superDispatchTouchEvent 方法if (getWindow().superDispatchTouchEvent(ev)) {return true;}//如果上面不进行处理,则调用 Activity 的 onTouchEvent 进行处理return onTouchEvent(ev);}public boolean onTouchEvent(MotionEvent event) {//如果点击区域外,则销毁当前界面,否则不进行任何处理if (mWindow.shouldCloseOnTouch(this, event)) {finish();return true;}return false;}}

接下来在看看PhoneWindow怎么处理

public class PhoneWindow extends Window implements MenuBuilder.Callback {// This is the top-level view of the window, containing the window decor.//窗口的顶层视图,相当于上面模型中的 跟ViewGroupprivate DecorView mDecor;@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}}public class DecorView extends FrameLayout{public boolean superDispatchTouchEvent(MotionEvent event) {//最终会调用 ViewGroup 的 dispatchTouchEvent 方法return super.dispatchTouchEvent(event);}}

先看看ViewGroup中的拦截逻辑

public abstract class ViewGroup extends View{@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {...boolean handled = false;...//进行安全校验和过滤,一般都为 trueif (onFilterTouchEventForSecurity(ev)) {...// 检测是否需要拦截final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}...if (!canceled && !intercepted) {...if (newTouchTarget == null && childrenCount != 0) {for (int i = childrenCount - 1; i >= 0; i--) {//倒叙遍历 子View...//将事件分发给 子Viewif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...//返回 true 表示该 view 处理事件,然后将该 view 赋值给 目标viewnewTouchTarget = addTouchTarget(child, idBitsToAssign);...}...}}...}}...return handled;}//进行一些安全性校验和过滤public boolean onFilterTouchEventForSecurity(MotionEvent event) {//noinspection RedundantIfStatementif ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {// Window is obscured, drop this touch.return false;}return true;}//是否拦截事件,在 ViewGroup 中一般返回的 falsepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false;}}

在看看ViewGroup的分发逻辑

public abstract class ViewGroup extends View{private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;...if (child == null) {//调用 View 的 dispatchTouchEvent 方法handled = super.dispatchTouchEvent(event);} else {//调用 子View 的 dispatchTouchEvent 方法handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);...return handled}}

上面事件已经从Activity分发到了ViewGroup,再从ViewGroupdispatchTransformedTouchEvent方法调用了子ViewdispatchTouchEvent方法,接下来在看看 View 中对于事件是怎么处理的。

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;//当前View是否可见(未被其他窗口遮盖住,且未隐藏)if (onFilterTouchEventForSecurity(event)) {...ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {//如果设置了 OnTouchListener//则先响应 OnTouchListener.onTouch 方法result = true;}// 当 onTouch 返回 false,在执行 onTouchEvent 处理相关事件,如果处理,则返回 trueif (!result && onTouchEvent(event)) {result = true;}...}...return result;}public boolean onTouchEvent(MotionEvent event) {...final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;...//如果 View 可点击 或者 可长按,则最终一定 return trueif (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {//抬起,判断是否处理点击事件case MotionEvent.ACTION_UP:break;//按下,处理长按事件case MotionEvent.ACTION_DOWN:break;//移动,检测触摸是否划出了控件,移除响应事件case MotionEvent.ACTION_MOVE:break;}return true;}...return false;}}

事件分发总结

事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false, View没有onlnterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的clickable和longClickable同时为false), View的longClickable默认都为false, clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false.View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回trueonClick会响应的前提是当前View是可点击的,并且收到了ACTION-DOWN和ACTION-_UP的事件,并且受长按事件影响,当长按事

件返回true时, onClick不会响应。onLongClick在ACTION-DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束正常情况下,一个事件序列只能被一个view拦截并且消耗。某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onlnterceptTouchEvent不会再调用。某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用)。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。