View体系与自定义View(三)—— View的事件分发机制
1. 分析Activity
的构成
一个Activity
包含一个Window
对象,这个对象是由PhoneWindow
来实现的。PhoneWindow
将DecorView
作为整个应用窗口的根View
。 而这个DecorView
又将屏幕划分为两个区域:一个是TitleView
,另一个是ContentView
,而平时应用所写的布局正式展示在ContentView
中:
decor [deɪˈkɔːr] 装饰,布置
上面说到Activity
的构成,以下是DecorView
如何被加载到Window
中:
instrumentation [ˌɪnstrəmenˈteɪʃn] 使用仪器 schedule [ˈskedʒuːl] 安排,预定 perform [pərˈfɔːrm] 执行,运行
attach [ə'tætʃ] 连接 decor [deɪˈkɔːr] 装饰,布置
Instrumentation
是管理Application
、Activity
、Service
等组件的创建,生命周期的调用等。
以下是Activity.attach
的部分源码:
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2 {private Window mWindow;final void attach(...) {mWindow = new PhoneWindow(this, window, activityConfigCallback); // 1}
}public class PhoneWindow extends Window implements MenuBuilder.Callback {private DecorView mDecor;public PhoneWindow(Context context, Window preservedWindow,ActivityConfigCallback activityConfigCallback) {mDecor = (DecorView) preservedWindow.getDecorView(); // 2}
}
mWindow
指的就是PhoneWindow
,PhoneWindow
是继承抽象类Window
的。
在Activity
中会调用setContentView
方法来加载布局,以下是setContentView
的源码:
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2 {private Window mWindow;public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID); // 2initWindowDecorActionBar();}public Window getWindow() { // 3return mWindow;}
}
Activity
中的setContentView()
方法调用的是PhoneWindow.setContent(layoutResID)
以下是PhoneWindow
的源码,PhoneWindow
是继承抽象类Window
的:
decor [deɪˈkɔːr] 装饰,布置 inflater [ɪnˈfleɪtər] 增压泵;充气者
public class PhoneWindow extends Window implements MenuBuilder.Callback {private DecorView mDecor;ViewGroup mContentParent;private LayoutInflater mLayoutInflater;@Overridepublic void setContentView(int layoutResID) {if (mContentParent == null) {installDecor(); // 1} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext());transitionTo(newScene);} else {mLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;}private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {mDecor = generateDecor(-1); // 2} if (mContentParent == null) {mContentParent = generateLayout(mDecor); // 3}}// 2.1 创建了一个DecorView,这个DecorView就是Activity中的根Viewprotected DecorView generateDecor(int featureId) {return new DecorView(context, featureId, this, getAttributes());}// 2.2 DecorView是PhoneWindow的内部类,并且继承了FrameLayoutpublic class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { }// 3.1 根据不同的情况加载不同的布局给layoutResourceprotected ViewGroup generateLayout(DecorView decor) {int layoutResource;int features = getLocalFeatures();if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {if (mIsFloating) {TypedValue res = new TypedValue();getContext().getTheme().resolveAttribute(R.attr.dialogTitleIconsDecorLayout, res, true);layoutResource = res.resourceId;} else {layoutResource = R.layout.screen_title_icons;}} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {if (mIsFloating) {TypedValue res = new TypedValue();getContext().getTheme().resolveAttribute(R.attr.dialogTitleDecorLayout, res, true);layoutResource = res.resourceId;} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {layoutResource = a.getResourceId(R.styleable.Window_windowActionBarFullscreenDecorLayout,R.layout.screen_action_bar);} else {layoutResource = R.layout.screen_title; // 3.2}}return contentParent;}}
以下是screen_title.xml
的源码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:fitsSystemWindows="true"><!-- Popout bar for action modes --><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><FrameLayoutandroid:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize"style="?android:attr/windowTitleBackgroundStyle"><TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle"android:background="@null"android:fadingEdge="horizontal"android:gravity="center_vertical"android:layout_width="match_parent"android:layout_height="match_parent" /></FrameLayout><!--你要的R.id.content在这里--><FrameLayout android:id="@android:id/content"android:layout_width="match_parent" android:layout_height="0dip"android:layout_weight="1"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
ViewStub
是用来显示ActionBar
的。下面的两个FrameLayout
:一个是title
用来显示标题;另一个是content
,用来显示内容。
stub [stʌb] 存根;烟蒂;树桩;断株
View
的加载流程(setConentView
)
DecorView
初始化- 通过
LayoutInflate
对象加载View
,主要步骤是:- 通过
xml
的Pull
方式去解析xml
布局文件,获取布局信息,并进行缓存 - 根据
xml
的tag
标签通过反射创建View
逐层构建View
- 递归构建其中的子
View
,并将子View
添加到父容器中
- 通过
2. View
的事件分发机制
点击事件用MotionEvent
表示,当一个点击事件产生后,事件最先传递给Activity
。
当点击屏幕时,就会产生点击事件,这个事件被封装成了一个类:MotionEvent
。而当这个MotionEvent
产生后,系统就会将这个MotionEvent
传递给View
的层级,MotionEvent
在View
中的层级传递过程就是点击事件分发。
MotionEvent
对象产生一系列事件,它有四种状态:
MotionEvent.ACTION_DOWN
:手指按下屏幕的瞬间(一切事件的开始)MotionEvent.ACTION_MOVE
:手指在屏幕上移动MotionEvent.ACTION_UP
:手指离开屏幕瞬间MotionEvent.ACTION_CANCEL
:取消手势,一般由程序产生,不会由用户产生
Android
中的行为onClick
、onLongClick
、onScroll
、onFling
等等,都是由许多个Touch
事件构成的(一个ACTION_DOWN
, n
个ACTION_MOVE
,1
个ACTION_UP
)。
motion [ˈmoʊʃn] 动作;移动;手势;请求;意向;议案
Android
的UI
界面由Activity
、ViewGroup
、View
及其派生类组成,事件也就在Activity
、ViewGroup
、View
中进行传递。
当屏幕被触摸,首先会通过硬件产生触摸事件传入内核,然后走到FrameWork
层,最后经过一系列事件处理到达ViewRootImpl
的processPointerEvent
方法,而processPointerEvent
方法中的mView
就是DecorView
。
当点击事件传递到当前的Activity
时,具体的事件处理工作都是交由Activity
中的PhoneWindow
完成的,然后PhoneWindow
再把事件处理工作交给DecorView
(DecorView
一般就是当前界面的底层容器,即setContentView
所设置的View
的父容器),之后再由DecorView
将事件处理工作交给ViewGroup
。
以下是源码:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks {View mView;private int processPointerEvent(QueuedInputEvent q) {boolean handled = mView.dispatchPointerEvent(event);}}public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {public final boolean dispatchPointerEvent(MotionEvent event) {if (event.isTouchEvent()) {return dispatchTouchEvent(event);} else {return dispatchGenericMotionEvent(event);}}}public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {final Window.Callback cb = mWindow.getCallback();return cb != null && !mWindow.isDestroyed() && mFeatureId < 0? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);}}public class Activity extends ContextThemeWrapper implements
Window.Callback, KeyEvent.Callback, Window.OnWindowDismissedCallback {// 1.public boolean dispatchTouchEvent(MotionEvent ev) { if (getWindow().superDispatchTouchEvent(ev)) { // 1.1return true;}return onTouchEvent(ev);}// 2public boolean onTouchEvent(MotionEvent event) {if (mWindow.shouldCloseOnTouch(this, event)) {finish();return true;}return false;}}public abstract class Window {public abstract boolean superDispatchTouchEvent(MotionEvent event);
}public class PhoneWindow extends Window implements MenuBuilder.Callback {private DecorView mDecor;@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { // 1.2return mDecor.superDispatchTouchEvent(event);}
}public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {public boolean superDispatchTouchEvent(MotionEvent event) { // 1.3return super.dispatchTouchEvent(event);}
}public class FrameLayout extends ViewGroup { }public abstract class ViewGroup extends View implements ViewParent, ViewManager {public boolean dispatchTouchEvent(MotionEvent ev) { // 1.4intercepted = onInterceptTouchEvent(ev);}public boolean onInterceptTouchEvent(MotionEvent ev) { }
}
Activity
只有dispatchTouchEvent
和onTouchEvent
方法。
ViewRootImpl.DecorView.dispatchPointerEvent
- > ViewRootImpl.DecorView.dispatchTouchEvent
-> Activity.dispatchTouchEvent()
-> PhoneWindow.superDispatchTouchEvent
-> DecorView.superDispatchTouchEvent
-> ViewGroup.dispatchTouchEvent()
。 如果ViewGroup.dispatchTouchEvent()
返回false
则调用onTouchEvent
方法,true
表示事件被消费掉了。
但是为什么DecorView
就走了两次?—— 主要原因就是解耦
ViewRootImpl
并不知道有Activity
的存在,它只是持有DecorView
,所以传给了DecorView
;DecorView
知道Activity
的存在,所以传给了Activity
;Activity
不知道DecorView
的存在,它只是持有PhoneWindow
,所以就形成了这样一段调用链;
Android
事件响应机制是先分发(先由外部的View
接收,然后依次传递给其内层的最小View
)再处理(从最小View
单元(事件源)开始依次向外层传递)的形式实现的。其复杂性表现在:可以控制每层事件是否继续传递(分发和拦截协同实现),以及事件的具体消费(事件分发也具有事件消费能力)。
事件分发过程由dispatchTouchEvent
、onInterceptTouchEvent
、onTouchEvent
协作完成:
dispatchTouchEvent
:分发事件,当点击事件能够传递给当前View
时,该方法就会被调用onInterceptTouchEvent
:事件拦截,只存在于ViewGroup
中,普通的View
没有此方法。在ViewGroup
的dispatchTouchEvent
内部调用onTouchEvent
:处理点击事件,在dispatchTouchEvent
中调用
dispatch [dɪˈspætʃ] 派遣,发送;迅速处理,快速办妥;杀死,处决 intercept [ˌɪntərˈsept] 拦截;截断;窃听
以下是Activity
的dispatchTouchEvent
方法和onTouchEvent
方法:
public class Activity extends ContextThemeWrapper implements Window.Callback, KeyEvent.Callback {public boolean dispatchTouchEvent(MotionEvent ev) {if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}public boolean onTouchEvent(MotionEvent event) {if (mWindow.shouldCloseOnTouch(this, event)) {finish();return true;}return false;}
}
以下是View
的dispatchTouchEvent
方法和onTouchEvent
方法:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {public boolean dispatchTouchEvent(MotionEvent event) {boolean result = false;if (onFilterTouchEventForSecurity(event)) {...if (!result && onTouchEvent(event)) {result = true;}}return result;}public boolean onTouchEvent(MotionEvent event) { }
}
以下是ViewGroup
的dispatchTouchEvent
方法和onInterceptTouchEvent
方法:
public abstract class ViewGroup extends View implements ViewParent, ViewManager {public boolean dispatchTouchEvent(MotionEvent ev) {intercepted = onInterceptTouchEvent(ev);}public boolean onInterceptTouchEvent(MotionEvent ev) { }
}
View
没有onInterceptTouchEvent
方法,一旦有点击事件传递給它,它就会处理。
说明:
Activity
的dispatchTouchEvent
只有return super.dispatchTouchEvent(ev)
才往下走,返回true
或者false
事件就被消费了(终止传递);- 如果事件不被中断,整个事件流向就是一个
U
型图。 如果没有对控件里面的方法进行重写或更改返回值,而直接用super
调用父类的默认实现,那么整个事件流向应该是从Activity -> ViewGroup -> View
从上往下调用dispatchTouchEvent
方法,一直到叶子节点(View
)的时候,再由View -> ViewGroup -> Activity
从下往上调用onTouchEvent
方法; - 对于
dispatchTouchEvent
、onTouchEvent
、onInterceptTouchEvent
,
ViewGroup
和View
的这些方法的默认实现就是会让整个事件按照U
型完整走完,所以return super.xxxxxx()
就会让事件依照U
型的方向的完整走完整个事件流动路径,中间不做任何改动,不回溯、不终止,每个环节都走到; dispatchTouchEvent
和onTouchEvent
一旦return true
,事件就停止传递了,没有谁能再收到这个事件;return false
的时候,事件都回传给父控件的onTouchEvent
处理;- 对于
dispatchTouchEvent
返回false
的含义应该是:事件停止往子View
传递和分发,同时开始往父控件回溯(父控件的onTouchEvent
开始从下往上回传直到某个onTouchEvent return true
), 事件分发机制就像递归,return false
的意义就是递归停止然后开始回溯; - 对于
onTouchEvent return false
就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动; - 对于
onInterceptTouchEvent
,intercept
的意思就拦截,每个ViewGroup
每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理),如果要自己处理那就在onInterceptTouchEvent
方法中return true
就会交给自己的onTouchEvent
的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,因为子View
也需要这个事件,所以onInterceptTouchEvent
拦截器return super.onInterceptTouchEvent()
和return false
是一样的,是不会拦截的,事件会继续往子View
的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
方法的;
总结:
- 对于
dispatchTouchEvent
和onTouchEvent
,return true
是终结事件传递,retrun false
是回溯到父View
的onTouchEvent
方法; ViewGroup
想把自己分发给自己的onTouchEvent
,需要拦截器onInterceptTouchEvent
方法return true
把事件拦截下来;ViewGroup
的拦截器onInterceptTouchEvent
默认是不拦截的,所以return super.onInterceptTouchEvent = return false
;View
没有拦截器,为了让View
可以把事件分发给自己的onTouchEvent
,View
的dispatchTouchEvent
默认实现(super
)就是把事件分发给自己的onTouchEvent
;
3 验证View
事件分发机制
以下是MyView
的代码,继承View
:
class MyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {override fun dispatchTouchEvent(event: MotionEvent?): Boolean {Log.e("CAH", "MyView: dispatchTouchEvent")return super.dispatchTouchEvent(event)}override fun onTouchEvent(event: MotionEvent?): Boolean {Log.e("CAH", "MyView: onTouchEvent")return super.onTouchEvent(event)}}
MyViewGroup01
和MyViewGroup02
是一样的代码,这里以MyViewGroup01
为例,继承ViewGroup
:
class MyViewGroup01 @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e("CAH", "MyViewGroup01: dispatchTouchEvent")return super.dispatchTouchEvent(ev)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {Log.e("CAH", "MyViewGroup01: onInterceptTouchEvent")return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(event: MotionEvent?): Boolean {Log.e("CAH", "MyViewGroup01: onTouchEvent")return super.onTouchEvent(event)}}
MyView
和MyViewGroup
布局文件:
<com.example.kotlintest.MyViewGroup02 xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="200dp"android:layout_height="200dp"android:background="@android:color/holo_green_light"><com.example.kotlintest.MyViewGroup01android:layout_width="100dp"android:layout_height="100dp"android:background="@android:color/holo_purple"><com.example.kotlintest.MyViewandroid:layout_width="50dp"android:layout_height="50dp"android:background="@android:color/holo_red_light" /></com.example.kotlintest.MyViewGroup01></com.example.kotlintest.MyViewGroup02>
运行:
点击MyView
(红色部分):先接收事件的是父容器(MyViewGroup02
),继续分发,而事件的分发过程分为两个步骤:分发过程和处理过程。其正常的分发结果为:
// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
// CAH: MyViewGroup01: onInterceptTouchEvent
// CAH: MyView: dispatchTouchEvent// 处理过程
// CAH: MyView: onTouchEvent
// CAH: MyViewGroup01: onTouchEvent
// CAH: MyViewGroup02: onTouchEvent
3.1 dispatchTouchEvent
(分发事件)
如果在MyViewGroup01
的dispatchTouchEvent
方法中返回true
,表示需要在MyViewGroup01
消费了整个事件,即不会再分发,也不会再处理。dispatchTouchEvent
方法中返回true
的打印信息:
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
如果在MyViewGroup01
的dispatchTouchEvent
方法中返回false
,表示在MyViewGroup01
点击事件在本层不再继续进行分发,并交由上层控件的onTouchEvent
方法进行消费。dispatchTouchEvent
方法中返回false
的打印信息:
// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent// 处理过程
// CAH: MyViewGroup02: onTouchEvent
3.2 onInterceptTouchEvent
(拦截事件)
如果在MyViewGroup01
的onInterceptTouchEvent
方法中返回true
,表示需要在MyViewGroup01
拦截这个点击事件,不再继续往下分发,即MyView
不再执行dispatchTouchEvent
方法。但是只是分发结束了而已,接着开始处理事件。下面是onInterceptTouchEvent
方法中返回true
的打印信息:
// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
// CAH: MyViewGroup01: onInterceptTouchEvent// 处理过程
// CAH: MyViewGroup01: onTouchEvent
// CAH: MyViewGroup02: onTouchEvent
如果在MyViewGroup01
的onInterceptTouchEvent
方法中返回false
,表示需要在MyViewGroup01
不会拦截这个点击事件,继续往下分发。下面是onInterceptTouchEvent
方法中返回false
的打印信息:
// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
// CAH: MyViewGroup01: onInterceptTouchEvent
// CAH: MyView: dispatchTouchEvent// 处理过程
// CAH: MyView: onTouchEvent
// CAH: MyViewGroup01: onTouchEvent
// CAH: MyViewGroup02: onTouchEvent
3.3 onTouchEvent
(消费事件)
如果MyViewGroup01
的onTouchEvent
方法中返回true
,表示MyViewGroup01
可以将该事件直接消费掉了,即分发结束后,处理事件的时候,直接处理到MyViewGroup01
就可以结束了。下面是onTouchEvent
方法中返回true
的打印信息:
// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
// CAH: MyViewGroup01: onInterceptTouchEvent
// CAH: MyView: dispatchTouchEvent// 处理过程
// CAH: MyView: onTouchEvent
// CAH: MyViewGroup01: onTouchEvent
如果MyViewGroup01
的onTouchEvent
方法中返回false
,表示MyViewGroup01
不可以将该事件直接消费掉,即事件继续往上处理。下面是onTouchEvent
方法中返回false
的打印信息:
// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
// CAH: MyViewGroup01: onInterceptTouchEvent
// CAH: MyView: dispatchTouchEvent// 处理过程
// CAH: MyView: onTouchEvent
// CAH: MyViewGroup01: onTouchEvent
// CAH: MyViewGroup02: onTouchEvent
4 对于view.onTouchEvent
,OnClickListerner.onClick
和OnTouchListener.onTouch
的优先级
View.dispatchTouchEvent
-> OnTouchListener.onTouch
-> View.onTouchEvent
-> View.performClick
-> OnClickListener.onClick
以下是View
的源码:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {public boolean dispatchTouchEvent(MotionEvent event) {boolean result = false;if (onFilterTouchEventForSecurity(event)) {ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {result = true;}if (!result && onTouchEvent(event)) {result = true;}}return result;}public boolean onTouchEvent(MotionEvent event) {switch (action) {case MotionEvent.ACTION_UP:if (!post(mPerformClick)) {performClick();}break;}}public boolean performClick() {final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {li.mOnClickListener.onClick(this);result = true;} else {result = false;}return result;}static class ListenerInfo {private OnTouchListener mOnTouchListener;public OnClickListener mOnClickListener;}public interface OnTouchListener {boolean onTouch(View v, MotionEvent event);}public interface OnClickListener {void onClick(View v);}}
onTouch
方法是View
的OnTouchListener
接口中定义的方法;onClick
方法是View
的OnClickListener
接口中定义的方法;
- 在
View.dispatchTouchEvent
方法中,OnTouchListener.onTouch
方法优先级比View.onTouchEvent
方法的优先级高,会先触发; - 如果
OnTouchListener.onTouch
方法返回false
会接着触发View.onTouchEvent
方法;返回true
,则View.onTouchEvent
方法不会被调用; OnClickListerner.onClick
方法是在View.onTouchEvent
的MotionEvent.ACTION_UP
事件通过View.performClick
方法触发的;
因此,OnTouchListener.onTouch
方法如果返回true
,则不会执行View.onTouchEvent
方法,也就更不会执行OnClickListener.onClick
方法,如果返回false
,则两个都会执行。
5 事件序列
事件序列:同一个事件序列是指从手指触摸屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中产生的一系列事件,这个事件以DOWN
事件开始,中间含有不定数量的MOVE
事件,最终以UP
事件结束
在ViewGroup.dispatchTouchEvent
中消费ACTION_DOWN
事件,ACTION_UP
事件是怎么传递?
正常情况下,一个事件序列只能被一个View
拦截且消耗,因为一旦某个View
拦截了此事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且它的onInterceptTouchEvent
不会再被调用。 因此同一个事件序列中的事件不能分别由两个View
同时处理,但是通过特殊的手段可以做到,比如一个View
将本该字自己理的事件通过onTouchEvent
强行传递给其他View
处理。
Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onInterceptTouchEvent()
-> view1.dispatchTouchEvent()
-> view1.onTouchEvent()
-> ViewGroup1.onTouchEvent()
-> Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onTouchEvent()
如果Activity
、ViewGroup
和View
都不消费ACTION_DOWN
,那么ACTION_UP
事件是怎么传递的?
某个View
一旦开始处理事件,如果它不消耗ACTION_DOWN
事件,那么同一时间序列中的其它事件也不会交给它来处理,事件会重新交给它的父元素去处理,即父元素的onTouchEvent
会被调用。 如果View
不消耗除ACTION_DOWN
以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent
并不会被调用,并且当前View
可持续收到后续的事件,最终这些消失的点击事件会传递给Activity
处理。
ACTION_DOWN
:Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onInterceptTouchEvent()
-> view1.dispatchTouchEvent()
-> view1.onTouchEvent()
-> ViewGroup1.onTouchEvent()
-> Activity.onTouchEvent()
;
ACTION_MOVE
:Activity.dispatchTouchEvent()
-> Activity.onTouchEvent()
-> 消费
ACTION_CANCEL
什么时候触发?
- 如果在父
View
中拦截ACTION_UP
或ACTION_MOVE
,在父视图拦截消息的瞬间,就指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL
事件; - 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现
ACTION_CANCEL
;
同时对父View
和子View
设置点击方法,优先响应哪个?
优先响应子View
, 如果先响应父View
,那么子View
将永远无法响应。 父View
要优先响应事件,必须先调用onInterceptTouchEven
对事件进行拦截,那么事件不会再往下传递,直接交给父View
的onTouchEvent
处理。
以下是ViewGroup
的dispatchTouchEvent()
源码:
public abstract class ViewGroup extends View implements ViewParent, ViewManager {private TouchTarget mFirstTouchTarget;@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (actionMasked == MotionEvent.ACTION_DOWN) { // 1. cancelAndClearTouchTargets(ev);resetTouchState();}// 2if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 2.1final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 2.2if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); } else {intercepted = false;}} else {intercepted = true;}}// 3public 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;}
}
注释1
:这里首先判断事件是否为DOWN
事件,如果是,则进行初始化,这事因为一个完整的事件序列是以DOWN
开始,以UP
结束的。
FLAG_DISALLOW_INTERCEPT
标志位,它主要是禁止ViewGroup
拦截除了DOWN
之外的事件,一般通过子View
的requestDisallowInterceptTouchEvent
来设置。
requestDisallowInterceptTouchEvent
的调用时机或者点击事件被拦截,但是想传到下面的View
,如何操作?
重写子类的requestDisallowInterceptTouchEvent()
方法,返回true
就不会执行父类的onInterceptTouchEvent()
,可将点击事件传到下面的View,
剥夺了父View
对除了ACTION_DOWN
以外的事件的处理权。
当ViewGroup
要拦截事件的时候,那么后续的事件都交给它处理,而不用调用onInterceptTouchEvent
方法了。所以onInterceptTouchEvent
方法并不是每次事件都会调用。
参考
https://blog.csdn.net/qq_32534441/article/details/103634329
https://www.jianshu.com/p/e99b5e8bd67b
View体系与自定义View(三)—— View的事件分发机制相关推荐
- Android进阶之光读书笔记——第三章:View体系与自定义View
第三章 View体系与自定义View 本章将介绍Android中十分重要的View,在多本书中View是必讲的一节,Android群英传就讲了不少的View的知识,那么在这里我们再去复习一遍吧 3.1 ...
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...
前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象
前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...
- View的事件体系之三 android事件分发机制详解(下)
接着上一篇来分析事件分发机制,在看了各位大牛的关于事件分发机制的分析后茅塞顿开,之前看过好几遍郭霖,弘扬以及玉刚大神关于事件体系的讲解,一直看不懂,比较模糊,最近复习时,看到一篇博文,写的相当精彩,看 ...
- Android View体系(五)从源码解析View的事件分发机制
Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Sc ...
- 自定义View(二)--表层浅析View的事件分发机制和滑动冲突
转载请注明出处:From李诗雨:http://blog.csdn.net/cjm2484836553/article/details/54387722 不诗意的女程序猿不是好厨师~ 这篇文章来得有些曲 ...
- 安卓自定义View进阶-事件分发机制原理
之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部分的需求都能满足,但是关于View还有很多知识 ...
- 安卓自定义View进阶-事件分发机制原理【转自 app架构师 微信公众号】
注意:本文中所有源码分析部分均基于 API23(Android 6.0) 版本,由于安卓系统源码改变很多,可能与之前版本有所不同,但基本流程都是一致的. 为什么要有事件分发机制? 安卓上面的View是 ...
- 安卓自定义View进阶-事件分发机制详解
原文地址:http://www.gcssloop.com/customview/dispatch-touchevent-source Android 事件分发机制详解,在上一篇文章 事件分发机制原理 ...
最新文章
- 腾讯!阿里!大二男生斩获4家头部科技公司实习offer!凭啥?
- python是属于it界吗_转行IT行业,Python是不是一个好的选择?
- Java Package getPackage()方法与示例
- 总结一下这一年来的心得体会
- Golang实践录:静态资源文件整合:web服务
- linux 下把整数转化为字符串
- eclipse java 报错信息_解决Eclipse启动时报Initializing Java Tooling异常信息
- HTML5网络视频webm格式制作
- Failed to instantiate [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans]: F
- 圣经闪卡 - Holy Bible Flash Cards
- DPDK Release 20.11
- 计算机发展史评课议课稿,评课稿模板5篇
- 使用Stratasys创建3D打印医学模型的工作流程
- 各种HIC处理数据之间的相互转化
- 【博弈】叉圈棋永远都是平局
- UGUI-- 图集制作
- UBUNTU ROS 编译后无法rosrun package文件(已解决)
- JS/JavaScript中两个等号 == 和 三个等号 === 的区别
- 宠物识别api接口全开放:狗脸识别、猫脸识别、鼻纹识别、品种识别、相似度比对、图片质量检测
- 利用freemarker模板导出复杂excel可带图片
热门文章
- 5个图表软件,职场人士提高效率必备
- SOA是什么?它的作用是什么?
- 学习C++的笔记(算法与数据结构要求
- 字符编码ANSI和ASCII区别、Unicode和UTF-8区别
- 深度优先探索算法 C++ 迷宫问题
- 【网盘】开启移动云计算时代,5大策略发展移动云计算
- 基于Unity引擎的2D像素风Roguelike地下城游戏模块之————背包系统
- 猿创征文|对象比较“==”与“hashCode()”的孽缘(java 小虚竹)
- 全球与中国立体声耳机市场深度研究分析报告
- Pikachu漏洞平台练习——PHP序列化与反序列化、PHP反序列化漏洞