1. 分析Activity的构成

一个Activity包含一个Window对象,这个对象是由PhoneWindow来实现的。PhoneWindowDecorView作为整个应用窗口的根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是管理ApplicationActivityService等组件的创建,生命周期的调用等。

以下是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指的就是PhoneWindowPhoneWindow是继承抽象类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,主要步骤是:
    • 通过xmlPull方式去解析xml布局文件,获取布局信息,并进行缓存
    • 根据xmltag标签通过反射创建View逐层构建View
    • 递归构建其中的子View,并将子View添加到父容器中

2. View的事件分发机制

点击事件用MotionEvent表示,当一个点击事件产生后,事件最先传递给Activity

当点击屏幕时,就会产生点击事件,这个事件被封装成了一个类:MotionEvent。而当这个MotionEvent产生后,系统就会将这个MotionEvent传递给View的层级,MotionEventView中的层级传递过程就是点击事件分发。

MotionEvent对象产生一系列事件,它有四种状态:

  • MotionEvent.ACTION_DOWN:手指按下屏幕的瞬间(一切事件的开始)
  • MotionEvent.ACTION_MOVE:手指在屏幕上移动
  • MotionEvent.ACTION_UP:手指离开屏幕瞬间
  • MotionEvent.ACTION_CANCEL :取消手势,一般由程序产生,不会由用户产生

Android中的行为onClickonLongClickonScrollonFling等等,都是由许多个Touch事件构成的(一个ACTION_DOWNnACTION_MOVE1ACTION_UP)。

motion [ˈmoʊʃn] 动作;移动;手势;请求;意向;议案

AndroidUI界面由ActivityViewGroupView 及其派生类组成,事件也就在ActivityViewGroupView中进行传递。

当屏幕被触摸,首先会通过硬件产生触摸事件传入内核,然后走到FrameWork层,最后经过一系列事件处理到达ViewRootImplprocessPointerEvent方法,而processPointerEvent方法中的mView就是DecorView

当点击事件传递到当前的Activity时,具体的事件处理工作都是交由Activity中的PhoneWindow完成的,然后PhoneWindow再把事件处理工作交给DecorViewDecorView一般就是当前界面的底层容器,即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只有dispatchTouchEventonTouchEvent方法。

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单元(事件源)开始依次向外层传递)的形式实现的。其复杂性表现在:可以控制每层事件是否继续传递(分发和拦截协同实现),以及事件的具体消费(事件分发也具有事件消费能力)。

事件分发过程由dispatchTouchEventonInterceptTouchEventonTouchEvent协作完成:

  • dispatchTouchEvent:分发事件,当点击事件能够传递给当前View时,该方法就会被调用
  • onInterceptTouchEvent:事件拦截,只存在于ViewGroup中,普通的View没有此方法。在ViewGroupdispatchTouchEvent内部调用
  • onTouchEvent:处理点击事件,在dispatchTouchEvent中调用

dispatch [dɪˈspætʃ] 派遣,发送;迅速处理,快速办妥;杀死,处决 intercept [ˌɪntərˈsept] 拦截;截断;窃听

以下是ActivitydispatchTouchEvent方法和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;}
}

以下是ViewdispatchTouchEvent方法和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) { }
}

以下是ViewGroupdispatchTouchEvent方法和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方法,一旦有点击事件传递給它,它就会处理。

说明:

  1. ActivitydispatchTouchEvent只有return super.dispatchTouchEvent(ev)才往下走,返回true或者false事件就被消费了(终止传递);
  2. 如果事件不被中断,整个事件流向就是一个U型图。 如果没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity -> ViewGroup -> View从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View -> ViewGroup -> Activity从下往上调用onTouchEvent方法;
  3. 对于dispatchTouchEventonTouchEventonInterceptTouchEvent
    ViewGroupView的这些方法的默认实现就是会让整个事件按照U型完整走完,所以return super.xxxxxx()就会让事件依照U型的方向的完整走完整个事件流动路径,中间不做任何改动,不回溯、不终止,每个环节都走到;
  4. dispatchTouchEventonTouchEvent一旦return true,事件就停止传递了,没有谁能再收到这个事件;return false的时候,事件都回传给父控件的onTouchEvent处理;
  5. 对于dispatchTouchEvent返回false的含义应该是:事件停止往子View传递和分发,同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传直到某个onTouchEvent return true), 事件分发机制就像递归,return false的意义就是递归停止然后开始回溯;
  6. 对于onTouchEvent return false就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动;
  7. 对于onInterceptTouchEventintercept的意思就拦截,每个ViewGroup每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理),如果要自己处理那就在onInterceptTouchEvent方法中return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()return false是一样的,是不会拦截的,事件会继续往子ViewdispatchTouchEvent传递;
  8. ViewGroupdispatchTouchEventreturn true是终结传递,return false是回溯到父ViewonTouchEvent,然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return truefalse都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent;
  9. 对于ViewdispatchTouchEvent return super.dispatchTouchEvent()的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true是终结,return false是回溯会父类的onTouchEvent怎样把事件分发给自己的onTouchEvent处理呢,那只能return super.dispatchTouchEventView类的dispatchTouchEvent方法默认实现就是能帮你调用View自己的onTouchEvent方法的;

总结:

  1. 对于dispatchTouchEventonTouchEventreturn true是终结事件传递,retrun false是回溯到父ViewonTouchEvent方法;
  2. ViewGroup想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true把事件拦截下来;
  3. ViewGroup的拦截器onInterceptTouchEvent默认是不拦截的,所以return super.onInterceptTouchEvent = return false
  4. View没有拦截器,为了让View可以把事件分发给自己的onTouchEventViewdispatchTouchEvent默认实现(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)}}

MyViewGroup01MyViewGroup02是一样的代码,这里以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)}}

MyViewMyViewGroup布局文件:

<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(分发事件)

如果在MyViewGroup01dispatchTouchEvent方法中返回true,表示需要在MyViewGroup01消费了整个事件,即不会再分发,也不会再处理。dispatchTouchEvent方法中返回true的打印信息:

// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent

如果在MyViewGroup01dispatchTouchEvent方法中返回false,表示在MyViewGroup01点击事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。dispatchTouchEvent方法中返回false的打印信息:

// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent// 处理过程
// CAH: MyViewGroup02: onTouchEvent

3.2 onInterceptTouchEvent(拦截事件)

如果在MyViewGroup01onInterceptTouchEvent方法中返回true,表示需要在MyViewGroup01拦截这个点击事件,不再继续往下分发,即MyView不再执行dispatchTouchEvent方法。但是只是分发结束了而已,接着开始处理事件。下面是onInterceptTouchEvent方法中返回true的打印信息:

// 分发过程
// CAH: MyViewGroup02: dispatchTouchEvent
// CAH: MyViewGroup02: onInterceptTouchEvent
// CAH: MyViewGroup01: dispatchTouchEvent
// CAH: MyViewGroup01: onInterceptTouchEvent// 处理过程
// CAH: MyViewGroup01: onTouchEvent
// CAH: MyViewGroup02: onTouchEvent

如果在MyViewGroup01onInterceptTouchEvent方法中返回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(消费事件)

如果MyViewGroup01onTouchEvent方法中返回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

如果MyViewGroup01onTouchEvent方法中返回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.onTouchEventOnClickListerner.onClickOnTouchListener.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方法是ViewOnTouchListener接口中定义的方法;onClick方法是ViewOnClickListener接口中定义的方法;

  • View.dispatchTouchEvent方法中,OnTouchListener.onTouch 方法优先级比View.onTouchEvent方法的优先级高,会先触发;
  • 如果OnTouchListener.onTouch方法返回false会接着触发View.onTouchEvent方法;返回true,则View.onTouchEvent方法不会被调用;
  • OnClickListerner.onClick方法是在View.onTouchEventMotionEvent.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()

如果ActivityViewGroupView都不消费ACTION_DOWN,那么ACTION_UP事件是怎么传递的?

某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件,那么同一时间序列中的其它事件也不会交给它来处理,事件会重新交给它的父元素去处理,即父元素的onTouchEvent会被调用。 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。

ACTION_DOWNActivity.dispatchTouchEvent() -> ViewGroup1.dispatchTouchEvent() -> ViewGroup1.onInterceptTouchEvent()-> view1.dispatchTouchEvent() -> view1.onTouchEvent() -> ViewGroup1.onTouchEvent() -> Activity.onTouchEvent();

ACTION_MOVEActivity.dispatchTouchEvent() -> Activity.onTouchEvent() -> 消费

ACTION_CANCEL什么时候触发?

  • 如果在父View中拦截ACTION_UPACTION_MOVE,在父视图拦截消息的瞬间,就指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件;
  • 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现ACTION_CANCEL

同时对父View和子View设置点击方法,优先响应哪个?

优先响应子View, 如果先响应父View,那么子View将永远无法响应。View要优先响应事件,必须先调用onInterceptTouchEven对事件进行拦截,那么事件不会再往下传递,直接交给父ViewonTouchEvent处理。

以下是ViewGroupdispatchTouchEvent()源码:

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之外的事件,一般通过子ViewrequestDisallowInterceptTouchEvent来设置。

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的事件分发机制相关推荐

  1. Android进阶之光读书笔记——第三章:View体系与自定义View

    第三章 View体系与自定义View 本章将介绍Android中十分重要的View,在多本书中View是必讲的一节,Android群英传就讲了不少的View的知识,那么在这里我们再去复习一遍吧 3.1 ...

  2. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  3. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  4. View的事件体系之三 android事件分发机制详解(下)

    接着上一篇来分析事件分发机制,在看了各位大牛的关于事件分发机制的分析后茅塞顿开,之前看过好几遍郭霖,弘扬以及玉刚大神关于事件体系的讲解,一直看不懂,比较模糊,最近复习时,看到一篇博文,写的相当精彩,看 ...

  5. Android View体系(五)从源码解析View的事件分发机制

    Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Sc ...

  6. 自定义View(二)--表层浅析View的事件分发机制和滑动冲突

    转载请注明出处:From李诗雨:http://blog.csdn.net/cjm2484836553/article/details/54387722 不诗意的女程序猿不是好厨师~ 这篇文章来得有些曲 ...

  7. 安卓自定义View进阶-事件分发机制原理

    之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部分的需求都能满足,但是关于View还有很多知识 ...

  8. 安卓自定义View进阶-事件分发机制原理【转自 app架构师 微信公众号】

    注意:本文中所有源码分析部分均基于 API23(Android 6.0) 版本,由于安卓系统源码改变很多,可能与之前版本有所不同,但基本流程都是一致的. 为什么要有事件分发机制? 安卓上面的View是 ...

  9. 安卓自定义View进阶-事件分发机制详解

    原文地址:http://www.gcssloop.com/customview/dispatch-touchevent-source Android 事件分发机制详解,在上一篇文章 事件分发机制原理  ...

最新文章

  1. 腾讯!阿里!大二男生斩获4家头部科技公司实习offer!凭啥?
  2. python是属于it界吗_转行IT行业,Python是不是一个好的选择?
  3. Java Package getPackage()方法与示例
  4. 总结一下这一年来的心得体会
  5. Golang实践录:静态资源文件整合:web服务
  6. linux 下把整数转化为字符串
  7. eclipse java 报错信息_解决Eclipse启动时报Initializing Java Tooling异常信息
  8. HTML5网络视频webm格式制作
  9. Failed to instantiate [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans]: F
  10. 圣经闪卡 - Holy Bible Flash Cards
  11. DPDK Release 20.11
  12. 计算机发展史评课议课稿,评课稿模板5篇
  13. 使用Stratasys创建3D打印医学模型的工作流程
  14. 各种HIC处理数据之间的相互转化
  15. 【博弈】叉圈棋永远都是平局
  16. UGUI-- 图集制作
  17. UBUNTU ROS 编译后无法rosrun package文件(已解决)
  18. JS/JavaScript中两个等号 == 和 三个等号 === 的区别
  19. 宠物识别api接口全开放:狗脸识别、猫脸识别、鼻纹识别、品种识别、相似度比对、图片质量检测
  20. 利用freemarker模板导出复杂excel可带图片

热门文章

  1. 5个图表软件,职场人士提高效率必备
  2. SOA是什么?它的作用是什么?
  3. 学习C++的笔记(算法与数据结构要求
  4. 字符编码ANSI和ASCII区别、Unicode和UTF-8区别
  5. 深度优先探索算法 C++ 迷宫问题
  6. 【网盘】开启移动云计算时代,5大策略发展移动云计算
  7. 基于Unity引擎的2D像素风Roguelike地下城游戏模块之————背包系统
  8. 猿创征文|对象比较“==”与“hashCode()”的孽缘(java 小虚竹)
  9. 全球与中国立体声耳机市场深度研究分析报告
  10. Pikachu漏洞平台练习——PHP序列化与反序列化、PHP反序列化漏洞