文章中出现的源码均基于8.0

前言

事件分发机制不仅仅是核心知识点更是难点,并且还是View的一大难题滑动冲突解决方法的理论基础,因此掌握好View的事件分发机制是十分重要的。

一、基本认识

1. 事件分发的对象

事件分发的对象是点击事件(Touch事件),而当用户触摸屏幕时,将产生点击事件。

事件类型分为四种,如下所示:

类型 说明
MotionEvent.ACTION_DOWN 手指刚接触屏幕,一般为事件的开始
MotionEvent.ACTION_MOVE 手指在屏幕移动,在移动的过程中会产生多个move事件
MotionEvent.ACTION_UP 手指从屏幕上松开的一瞬间
MotionEvent.ACTION_CANCEL 结束事件,非人为原因

同一个事件序列:指从手指刚接触屏幕,到手指离开屏幕的那一刻结束,在这一过程产生的一系列事件,这个序列一般以down事件开始,中间含有多个move事件,最终以up事件结束

2. 事件分发的本质

事件分发的本质,其实就是将点击事件(MotionEvent)传递到某个具体的View处理的整个过程

3. 事件分发的顺序

事件传递的顺序:Activity->Window->DecorView->ViewGroup->View。一个点击事件发生后,总是先传递给当前的Activity,然后通过Window传给DecorView再传给ViewGroup,最终传到View。

在《开发艺术探索》中的事件分发的顺序是:Activity->Window->View,而有的博客上的顺序是:Activity->ViewGroup->View,不过其实两者是一样的(下列会从源码进行分析)。Window是抽象类,其唯一实现类为PhoneWindow,PhoneWindow将事件直接传递给DecorView,而DecorView继承FrameLayout,FrameLayout又是ViewGroup的子类,所以兜兜转转最后也可以认为Window事件分发的实现其实是ViewGroup来实现的。所以也可以认为事件传递的顺序是:Activity->ViewGroup->View。

二、核心方法

View的事件分发机制主要由事件分发->事件拦截->事件处理三步来进行逻辑控制,很巧的这三步刚好对应了三个核心方法

1. 事件分发:dispatchTouchEvent

用来进行事件的分发,如果事件能够传递给当前View,则该方法一定会被调用。返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent的影响,表示是否消耗当前事件。

原型:public boolean dispatchTouchEvent(MotionEvent ev)

return:

  • ture:当前View消耗所有事件
  • false:停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,则事件将被系统消费,处理

2. 事件拦截:onInterceptTouchEvent

需注意的是在Activity,ViewGroup,View中只有ViewGroup有这个方法。故一旦有点击事件传递给View,则View的onTouchEvent方法就会被调用

在dispatchTouch Event内部使用,用来判断是否拦截事件。如果当前View拦截了某个事件,那么该事件序列的其它方法也由当前View处理,故该方法不会被再次调用,因为已经无须询问它是否要拦截该事件。

原型:public boolean onInterceptTouchEvent(MotionEvent ev)

return:

  • ture:对事件拦截,交给本层的onTouchEvent进行处理
  • false:不拦截,分发到子View,由子View的dispatchTouchEvent进行处理
  • super.onInterceptTouchEvent(ev):默认不拦截

3. 事件处理:onTouchEvent

在dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接受到剩下的事件,并且事件将重新交给它的父元素处理,即父元素的onTouchEvent会被调用

原型:public boolean onTouchEvent(MotionEvent ev)

return:

  • true:表示onTouchEvent处理后消耗了当前事件
  • false:不响应事件,不断的传递给上层的onTouchEvent方法处理,直到某个View的onTouchEvent返回true,则认为该事件被消费,如果到最顶层View还是返回false,则该事件不消费,将交由Activity的onTouchEvent处理。
  • super.onTouchEvent(ev):默认消耗当前事件,与返回true一致。

三、事件分发机制

在分析事件分发机制时,应该从事件分发的顺序入手一步一步解剖。从上文我们知道事件分发顺序为:Activity->Window->DecorView->ViewGroup->View。由于Window与DecorView可以看作是Activity->ViewGroup的过程,故这里将从三部分通过源码来分析事件分发机制:

  • Activity对点击事件的分发机制
  • ViewGroup对点击事件的分发机制
  • View对点击事件的分发机制

1. Activity事件的分发机制

我们知道,当一个点击事件发生时,事件总是最先传递到当前Activity中,由Activity的dispatchTouchEvent来进行事件分发。而Activity会将事件传递给Window对象来分发,Window对象再传递给DecorView。下面将进行源码分析来验证这个过程:

源码:Activity#dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent ev) {
//点击事件的开始一般为按下事件,所以总是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); }
//如果Activity所属Window的dispatchTouchEvent返回了ture
//则Activity.dispatchTouchEvent返回ture,点击事件停止往下传递
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果Window的dispatchTouchEvent返回了false,则点击事件传递给Activity.onTouchEvent
return onTouchEvent(ev);
}

上面代码为Activity中的dispatchTouchEvent的源码,通过源码我们可以知道当点击事件发生时,首先会执行onUserInteraction();这个方法又是什么呢?不急,我们跟踪下去。

	//空方法,当该Activity在栈顶时,触屏点击home,back,menu会触发此方法
public void onUserInteraction() {
}

从源码中可以看出在Activity中该方法为空方法,当该Activity在栈顶时,触屏点击home,back,menu会触发此方法,所以这个方法可以实现屏保功能。让我们回到Activity中的dispatchTouchEvent方法中,接着调用了getWindow().superDispatchTouchEvent(ev)方法将事件交给Activity所附属的Window进行分发,如果最终事件被消耗了,则返回true,如果事件没人处理,则Activity调用在自己的onTouchEvent()方法来处理事件。

getWindow是一个Window对象,在Window源码中我们可以发现其实Window就是一个抽象类,显而易见其方法自然是抽象方法,所以我们必须找出其具体实现类。

源码:Window#superDispatchTouchEvent

/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
...
//抽象方法
public abstract boolean superDispatchTouchEvent(MotionEvent event);
...
}

从源码中我们可以发现在Window类前面的注释中是有解释的,这时候就要考验偶们的英语能力辽!其实总体上来说还是挺容易理解的,其实我们只要关注后面一部分的注释就行。

The only existing implementation of this abstract class isandroid.view.PhoneWindow, which you should instantiate when needing a Window.

从这里我们可以知道它的唯一实现类就是PhoneWindow,废话不多说,我们直接看看PhoneWindow中superDispatchTouchEvent方法的实现是如何的呢?

源码:PhoneWindow#superDispatchTouchEvent

   private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

从源码中可以发现PhoneWindow将事件直接传递给了DecorView,而这个DecorView又是何方圣神呢?如下所示

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
......
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
......
} @RemoteView
public class FrameLayout extends ViewGroup {
......
}

其实DecorView就是我们通过setContentView设置布局的父容器,我们可以通过getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0)这个方式就能获取到setContentView中设置的布局。从DecorView的源码中可以发现DecorView是继承FrameLayout,而FrameLayout又是继承ViewGroup的,故DecorView的间接父类为ViewGroup,在DecorView中superDispatchTouchEvent方法是使用super来调用父类的dispatchTouchEvent,故等于调用ViewGroup的dispatchTouchEvent方法(从源码中我们可以得知FrameLayout并没有dispatchTouchEvent这个方法),于是DecorView将事件传递到了ViewGroup去处理。也可以这么说,事件已经传递到了顶级View也就是Activity中通过setContentView所设置的View(顶级View通常为ViewGroup)。

流程图如下:

到这里,我们也验证了前面提到的事件分发的顺序是:Activity->Window->DecorView->ViewGroup。那么ViewGroup又是如何将事件传递给View呢?让我们来继续分析!

2. ViewGroup事件的分发机制

从上面Activity事件的分发机制我们可以知道,ViewGroup事件分发机制是从dispatchTouchEvent()开始的,所以我们从这部分的源码开始分析,由于该方法代码量很多,下面将根据需要贴出相关代码:

源码:ViewGroup#dispatchTouchEvent

    @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
........
// Check for interception.
final boolean intercepted; //是否拦截
/*
* 当事件由ViewGroup的子元素处理时,mFirstTouchTarget会被赋值并指向子元素
*/
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;
}
}

从上面源码我们可以知道,ViewGroup判断是否要拦截只会是在ACTION_DOWN的时候,或者是mFirstTouchTarget != null。mFirstTouchTarget 从后面的代码才能知道其作用。它的作用就是:当事件被ViewGroup的某个子View处理时,mFirstTouchTarget 就会指向这个子View。所以当事件被这个ViewGroup拦截时,子类就不会处理这个事件,因此mFirstTouchTarget =null,那么这个时候ACTION_MOVE和ACTION_UP事件到来时,由于判断条件为false,将导致ViewGroup的onInterceptTouchEvent不会再被调用,然后intercepted被赋予true,所以同一事件序列的其它事件都会默认交给该ViewGroup来处理。在上面源码中我们还可以发现这么一句语句:

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

FLAG_DISALLOW_INTERCEPT是个标记位,这个标记位是通过 requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法来设置的,一般用在子View中。如果FLAG_DISALLOW_INTERCEPT被设置后,ViewGroup将无法拦截除了ACTION_DOWN以外的其它点击事件,这是因为ViewGroup在分发事件中,如果是ACTION_DOWN事件,将会重置FLAG_DISALLOW_INTERCEPT这个标记位。让我们来看看源码。

            if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
//
cancelAndClearTouchTargets(ev);
resetTouchState(); //对FLAG_DISALLOW_INTERCWPT进行重置 }

上面源码是在判断是否拦截的前面的,所以能够重置标记位,从这里我们也可以发现,当点击事件为ACTION_DOWN时,ViewGroup总是会调用自己的onInterceptTouchEvent来询问自己是否要拦截事件。

requestDisallowInterceptTouchEvent方法针对的是ACTION_DOWN以外的其他事件,并且是在不拦截ACTION_DOWN事件的情况下才会起作用。

接下来让我们瞧瞧ViewGroup不再拦截事件的时候,事件的分发情况,源码如下:

                        final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) { //遍历ViewGroup的所有子元素
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
/**
** 判断子元素是否能够接受到点击事件:
** 子元素是否在播动画和点击事件的坐标是否落在子元素的区域内
** 如果某个元素满足这两个条件,则事件交给它来处理
**/ if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
} newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
} resetCancelNextUpFlag(child); //dispatchTransformedTouchEvent实际调用的是子元素的dispatchTouchEvent方法 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign);
//记录ACTION_DOWN事件已经被处理了
alreadyDispatchedToNewTouchTarget = true;
break;
} // The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}

从上面代码中我们可以知道,不拦截事件时,首先会遍历ViewGroup的所有子元素,然后判断子元素是否能够接受到点击事件。判断的依据是:子元素是否在播放动画和点击事件的坐标是否落在子元素的区域内。如果找到一个目标子View来处理事件时,则调用dispatchTransformedTouchEvent()方法。来看看这个方法重要实现逻辑:

			  if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
........
handled = child.dispatchTouchEvent(event);
}

可以发现由于在上面中的child并不等于null,所以将直接调用子元素的dispatchTouchEvent方法,使得事件传递到子View上,然后继续分发。

你以为这样就结束了?答案肯定是没有,从上面源码中我们可以发现当子元素的dispatchTouchEvent返回true后,还有相应操作:

newTouchTarget = addTouchTarget(child, idBitsToAssign);
//记录ACTION_DOWN事件已经被处理了
alreadyDispatchedToNewTouchTarget = true;
break;

这几行代码完成了mFirstTouchTarget的赋值并终止了对子元素的遍历。如果子元素的dispatchTouchEvent返回false,则ViewGroup就会把事件分发给下一个元素(如果还有子元素的话),看到这你也许又纳闷了,mFirstTouchTarget的赋值?怎么没看见mFirstTouchTarget的影子呢,答案其实在addTouchTarget这个方法中:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}

这个方法可以看出其实mFirstTouchTarget是一种单链表结构,首先根据坐标点找到了目标子View,然后将子View放在链表头上,从而实现了mFirstTouchTarget!=null。

到这里就完成了ViewGroup一轮的事件分发了,然而还没有结束,如果遍历了所有子元素后事件都没有被合适处理呢?

没有合适处理包括了两种情况:

  • ViewGroup没有子元素
  • 子元素处理了点击事件,但是在dispatchTouchEvent中返回了false(默认是返回true,只有重写View的这个方法或者在onTouchEvent中返回了false)

那么这时候ViewGroup将会自己处理点击事件(当ViewGroup拦截了事件时也是做同样的处理)。

            if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}

可以看出这时候还是调用了dispatchTransformedTouchEvent方法,不过这时候第三个参数不是child而是null,所以会调用下面这句代码:

//dispatchTransformedTouchEvent
handled = super.dispatchTouchEvent(event);

super其实就是View中的dispatchTouchEvent方法,所以点击事件开始交由View来处理。

ViewGroup并没有调用onTouchEvent,ViewGroup也没有去重写onTouchEvent

流程图如下:

3. View事件的分发机制

从上面对ViewGroup事件分发机制可知,View事件分发机制是从dispatchTouchEvent开始的。

源码:View#dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
......
//判断窗口是否被遮挡,如果被遮挡则返回false,比如有时候两个View是会重叠的,导致其中一个被遮挡了。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判断是否设置了mOnTouchListener,如果设置了onTouchListener,且onTouch方法返回了ture,
//则result = true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//在result = ture情况下,就不会调用onTouchEvent,可见onTouchListener的优先级高于onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}

由于View是一个单独元素,没有子元素可以继续向下传递事件,只能自己处理事件,所以代码也会明显减少。从上面的源码中我们可以看到View对点击事件的处理过程,result代表是否消耗该事件,然后进行onTouchListener的判断,如果onTouchListenter中的onTouch方法返回了true,那么就不会再调用onTouchEvent方法,由此可见onTouchListener的优先级高于onTouchEvent。

然后来看看onTouchEvent的实现。

    public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //不可用状态下点击事件的处理,依然会消耗点击事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
//如果VIew设置了代理,将会执行代理的onTouchEvent方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
} if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
.....
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
......
//经过种种判断
performClickInternal();
break; case MotionEvent.ACTION_DOWN:
....
break; case MotionEvent.ACTION_CANCEL:
....
break; case MotionEvent.ACTION_MOVE:
....
break;
}
//若该控件可点击,就一定返回true
return true;
}
//若该控件不可点击,就一定返回false
return false;
}

从上面代码可以知道只要View的CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE有一个为true,那么它就会消耗该事件,不管它是不是DISABLE状态。然后假如控件可点击,就对四种事件类型进行相对应的处理,这里值得一说的是ACTION_UP事件,从源码中可以发现在ACTION_UP事件发生时,会触发performClickInternal方法。这个方法内部实现又是怎样的呢?如下:

    private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick(); return performClick();
}

我们可以发现最后还是会调用performClick,而在performClick内部中:

    public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick(); final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
} sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result;
}

只要我们通过setOnClickListener为View注册点击事件,那么就会给li.mOnClickListener赋值,则会调用onClick方法。

流程图如下:

从流程图我们可以发现onTouch,onTouchEvent,onClick的优先级:onTouch>onTouchEvent>onClick

总结

到这里,我们已经通过源码将点击事件的分发机制梳理一遍了。事件分发的大致过程如下:

  • 当一个点击事件发生后,总是先传递给当前的Activity,由Activity的dispatchTouchEvent进行分发,而Activity会将事件传递给Window,然后由Window的唯一实现类PhoneWindow将事件传递给DecorView,接着DecorView将事件传递给自己的父类ViewGroup,此时的ViewGroup就是通过setContentView所设置的View,故可以称为顶级View,这时候ViewGroup可能是自己处理该事件或者传递给子View,但是最终都会调用View的dispatchTouchEvent来处理事件。
  • 在View的dispatchTouchEvent中,如果设置了onTouchListener,会调用其onTouch方法,如果onTouch返回true,则不再调用onTouchEvent。如果有设置点击事件,则在onTouchEvent会调用onClick方法。如果子View的onTouchEvent返回了false,则表示不消耗事件,事件会回传给上一级的ViewGroup的onTouchEvent,如果所有的ViewGroup都没有返回true,则最终会回传到Activity的onTouchEvent。

参考博客:

本以为精通Android事件分发机制,没想到被面试官问懵了的更多相关文章

  1. Android事件分发机制(上)

    Android事件分发机制这个问题不止一个人问过我,每次我的回答都显得模拟两可,是因为自己一直对这个没有很好的理解,趁现在比较闲对这个做一点总结 举个例子: 你当前有一个非常简单的项目,只有一个Act ...

  2. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  3. 【自己定义控件】android事件分发机制

    自己定义Viewgrou中我们或许会常常碰到这种情况,2个子控件的事件冲突导致滑动没实用了.滑动反应非常慢,点击没用了,要划非常多次才移动一点点等等.或许我们第一反应就是百度,google去搜索下答案 ...

  4. Android 事件分发机制具体解释

    很多其它内容请參照我的个人网站: http://stackvoid.com/ 网上非常多关于Android事件分发机制的解释,大多数描写叙述的都不够清晰,没有吧来龙去脉搞清晰,本文将带你从Touch事 ...

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

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

  6. 【朝花夕拾】Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/10998855.html]谢谢! 在自定义View中,经常需要处理Android事件分发的问题, ...

  7. Android事件分发机制二:viewGroup与view对事件的处理

    前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...

  8. Android事件分发机制四:学了事件分发有什么用?

    " 学了事件分发,影响我CV大法吗?" " 影响我陪女朋友的时间" " ..... " 前言 Android事件分发机制已经来到第四篇了,在 ...

  9. Android事件分发机制五:面试官你坐啊

    前言 很高兴遇见你~ 事件分发系列文章已经到最后一篇了,先来回顾一下前面四篇,也当个目录: Android事件分发机制一:事件是如何到达activity的? : 从window机制出发分析了事件分发的 ...

随机推荐

  1. 2、SpringBoot整合之SpringBoot整合servlet

    SpringBoot整合servlet 一.创建SpringBoot项目,仅选择Web模块即可 二.在POM文件中添加依赖 <!-- 添加servlet依赖模块 --> <depen ...

  2. 尝鲜一试,Azure静态网站应用服务(Azure Static Web Apps) 免费预览,协同Github自动发布静态SPA

    背景 最近在浏览微软的文档的时候发现,微软喜欢用Hugo这个文档框架,有些技术产品的文档页面就用Hugo来做的,同时搭配Github + Azure Static Web Apps Service这个 ...

  3. webpack(2)webpack核心概念

    前言   本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具.当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph) ...

  4. ACM金牌选手整理的【LeetCode刷题顺序】

    算法和数据结构知识点图 首先,了解算法和数据结构有哪些知识点,在后面的学习中有 大局观,对学习和刷题十分有帮助. 下面是我花了一天时间花的算法和数据结构的知识结构,大家可以看看. 后面是为大家 精心挑 ...

  5. 小程序之app.json not found

    起因 最近在部署几款小程序时,发现ext.json文件会被忽略不上传,查了一下资料发现原来是需要升级开发者工具了. 没想到升级以后,再开发项目时,就报错 app.json not found 解决方法 ...

  6. php+redis实现全页缓存系统

    php redis 实现全页缓存系统之前的一个项目说的一个功能,需要在后台预先存入某个页面信息放到数据库,比如app的注册协议,用户协议,这种.然后在写成一个php页面,app在调用接口的时候访问这个 ...

  7. Spring Boot中文文档(官方文档翻译 基于1.5.2.RELEASE)

    作者:Phillip Webb, Dave Syer, Josh Long, Stéphane Nicoll, Rob Winch, Andy Wilkinson, Marcel Overdijk, ...

  8. 比较app版本大小----python

    def compare(a: str, b: str): '''比较两个版本的大小,需要按.分割后比较各个部分的大小''' lena = len(a.split('.')) # 获取版本字符串的组成部 ...

  9. ESP32-性能监控笔记

    基于ESP-IDF4.1 1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #i ...

  10. JS请求节流

    少废话,撸代码.欧耶! 1.节流器 // 对函数进行 节流 function throttle (fn, interval = 500) { let timer = null; let firstTi ...