Android事件分发机制源码解析,Android外包是如何进入阿里的

// 默认不拦截事件

return false;

}

这个方法比较长,只要把握住主要脉络,修枝剪叶后还是非常清晰的:

(1) 判断事件是够需要被ViewGroup拦截

首先会根据mGroupFlags判断是否可以执行onInterceptTouchEvent方法,它的值可以通过requestDisallowInterceptTouchEvent方法设置:

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {

// We’re already in this state, assume our ancestors are too

return;

}

if (disallowIntercept) {

mGroupFlags |= FLAG_DISALLOW_INTERCEPT;

} else {

mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;

}

// Pass it up to our parent

if (mParent != null) {

// 层层向上传递,告知所有父View不拦截事件

mParent.requestDisallowInterceptTouchEvent(disallowIntercept);

}

}

所以我们在处理某些滑动冲突场景时,可以从子View中调用父View的requestDisallowInterceptTouchEvent方法,阻止父View拦截事件。

如果view没有设置FLAG_DISALLOW_INTERCEPT,就可以进入onInterceptTouchEvent方法,判断是否应该被自己拦截,
ViewGroup的onInterceptTouchEvent直接返回了false,即默认是不拦截事件的,ViewGroup的子类可以重写这个方法,内部判断拦截逻辑。

**注意:**只有当事件类型是ACTION_DOWN或者mFirstTouchTarget不为空时,才会走是否需要拦截事件这一判断,如果事件是ACTION_DOWN的后续事件(如ACTION_MOVEACTION_UP等),且在传递ACTION_DOWN事件过程中没有找到目标子View时,事件将会直接被拦截,交给ViewGroup自己处理。mFirstTouchTarget的赋值会在下一节提到。

(2) 遍历所有子View,逐个分发事件:

执行遍历分发的条件是:当前事件是ACTION_DOWNACTION_POINTER_DOWN或者ACTION_HOVER_MOVE三种类型中的一个(后两种用的比较少,暂且忽略)。所以,如果事件是ACTION_DOWN的后续事件,如ACTION_UP事件,将不会进入遍历流程!

进入遍历流程后,拿到一个子View,首先会判断触摸点是不是在子View范围内,如果不是直接跳过该子View;
否则通过dispatchTransformedTouchEvent方法,间接调用child.dispatchTouchEvent达到传递的目的;

如果dispatchTransformedTouchEvent返回true,即事件被子View消费,就会把mFirstTouchTarget设置为child,即不为null,并将alreadyDispatchedToNewTouchTarget设置为true,然后跳出循环,事件不再继续传递给其他子View。

可以理解为,这一步的主要作用是,在事件的开始,即传递ACTION_DOWN事件过程中,找到一个需要消费事件的子View,我们可以称之为目标子View,执行第一次事件传递,并把mFirstTouchTarget设置为这个目标子View

(3) 将事件交给ViewGroup自己或者目标子View处理

经过上面一步后,如果mFirstTouchTarget仍然为空,说明没有任何一个子View消费事件,将同样会调用dispatchTransformedTouchEvent,但此时这个方法的View child参数为null,所以调用的其实是super.dispatchTouchEvent(event),即事件交给ViewGroup自己处理。ViewGroup是View的子View,所以事件将会使用View的dispatchTouchEvent(event)方法判断是否消费事件。

反之,如果mFirstTouchTarget不为null,说明上一次事件传递时,找到了需要处理事件的目标子View,此时,ACTION_DOWN的后续事件,如ACTION_UP等事件,都会传递至mFirstTouchTarget中保存的目标子View中。这里面还有一个小细节,如果在上一节遍历过程中已经把本次事件传递给子View,alreadyDispatchedToNewTouchTarget的值会被设置为true,代码会判断alreadyDispatchedToNewTouchTarget的值,避免做重复分发。

小结:
dispatchTouchEvent方法首先判断事件是否需要被拦截,如果需要拦截会调用onInterceptTouchEvent,若该方法返回true,事件由ViewGroup自己处理,不在继续传递。
若事件未被拦截,将先遍历找出一个目标子View,后续事件也将交由目标子View处理。
若没有目标子View,事件由ViewGroup自己处理。

此外,如果一个子View没有消费ACTION_DOWN类型的事件,那么事件将会被另一个子View或者ViewGroup自己消费,之后的事件都只会传递给目标子View(mFirstTouchTarget)或者ViewGroup自身。简单来说,就是如果一个View没有消费ACTION_DOWN事件,后续事件也不会传递进来。

View

现在回头看上一节的第2、3步,不管是对子View分发事件,还是将事件分发给ViewGroup自身,最后都殊途同归,调用到了View的dispatchTouchEvent,这就是我们这一节分析的目标。

public boolean dispatchTouchEvent(MotionEvent event) {

if (onFilterTouchEventForSecurity(event)) {

// 判断事件是否先交给ouTouch方法处理

if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&

mOnTouchListener.onTouch(this, event)) {

return true;

}

// onTouch未消费事件,传给onTouchEvent

if (onTouchEvent(event)) {

return true;

}

}

return false;

}

代码量不多,主要做了三件事:

  1. 若View设置了OnTouchListener,且处于enable状态时,会先调用mOnTouchListener的onTouch方法
  2. 若onTouch返回false,事件传递给onTouchEvent方法继续处理
  3. 若最后onTouchEvent也没有消费这个事件,将返回false,告知上层parent将事件给其他兄弟View

这样,我们的分析转到了View的onTouchEvent方法:

public boolean onTouchEvent(MotionEvent event) {

final int viewFlags = mViewFlags;

if ((viewFlags & ENABLED_MASK) == DISABLED) {

if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {

mPrivateFlags &= ~PRESSED;

refreshDrawableState();

}

// 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件

return (((viewFlags & CLICKABLE) == CLICKABLE ||

(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));

}

if (((viewFlags & CLICKABLE) == CLICKABLE ||

(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

switch (event.getAction()) {

case MotionEvent.ACTION_UP:

boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;

if ((mPrivateFlags & PRESSED) != 0 || prepressed) {

boolean focusTaken = false;

if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {

focusTaken = requestFocus();

}

if (prepressed) {

// The button is being released before we actually

// showed it as pressed. Make it show the pressed

// state now (before scheduling the click) to ensure

// the user sees it.

mPrivateFlags |= PRESSED;

refreshDrawableState();

}

if (!mHasPerformedLongPress) {

// This is a tap, so remove the longpress check

removeLongPressCallback();

// Only perform take click actions if we were in the pressed state

if (!focusTaken) {

// Use a Runnable and post this rather than calling

// performClick directly. This lets other visual state

// of the view update before click actions start.

if (mPerformClick == null) {

mPerformClick = new PerformClick();

}

if (!post(mPerformClick)) {

performClick();

}

}

}

if (mUnsetPressedState == null) {

mUnsetPressedState = new UnsetPressedState();

}

if (prepressed) {

postDelayed(mUnsetPressedState,

ViewConfiguration.getPressedStateDuration());

} else if (!post(mUnsetPressedState)) {

// If the post failed, unpress right now

mUnsetPressedState.run();

}

removeTapCallback();

}

break;

case MotionEvent.ACTION_DOWN:

mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {

break;

}

// Walk up the hierarchy to determine if we’re inside a scrolling container.

boolean isInScrollingContainer = isInScrollingContainer();

// For views inside a scrolling container, delay the pressed feedback for

// a short period in case this is a scroll.

if (isInScrollingContainer) {

mPrivateFlags |= PREPRESSED;

if (mPendingCheckForTap == null) {

mPendingCheckForTap = new CheckForTap();

}

postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

} else {

// Not inside a scrolling container, so show the feedback right away

mPrivateFlags |= PRESSED;

refreshDrawableState();

checkForLongClick(0);

}

break;

case MotionEvent.ACTION_CANCEL:

mPrivateFlags &= ~PRESSED;

refreshDrawableState();

removeTapCallback();

break;

case MotionEvent.ACTION_MOVE:

final int x = (int) event.getX();

final int y = (int) event.getY();

// Be lenient about moving outside of buttons

if (!pointInView(x, y, mTouchSlop)) {

// Outside button

removeTapCallback();

if ((mPrivateFlags & PRESSED) != 0) {

// Remove any future long press/tap checks

removeLongPressCallback();

// Need to switch from pressed to not pressed

mPrivateFlags &= ~PRESSED;

refreshDrawableState();

}

}

break;

}

return true;

}

return false;

}

public final boolean isFocusable() {

return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);

}

public final boolean isFocusableInTouchMode() {

return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);

}

onTouchEvent方法的主要流程如下:

  1. 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件,只是不会再走下面的流程;
  2. 如果View是enable的且处于可点击状态,事件将被这个View消费:
    在方法返回前,onTouchEvent会根据MotionEvent的不同类型做出不同响应,如调用refreshDrawableState()去设置View的按下效果和抬起效果等。
    这里我们主要关注ACTION_UP分支,这个分支内部经过重重判断之后,会调用到performClick方法:

public boolean performClick() {

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

if (mOnClickListener != null) {

playSoundEffect(SoundEffectConstants.CLICK);

mOnClickListener.onClick(this);

return true;

}

return false;

}

可以看到,如果设置了OnClickListener,就会回调我们的onClick方法,最终消费事件

总结

通过上面的源码解析,我们可以总结出事件分发的整体流程:

事件传递流程

下面做一个总体概括:

事件由Activity的dispatchTouchEvent()开始,将事件传递给当前Activity的根ViewGroup:mDecorView,事件开始自上而下进行传递,直至被消费。

事件传递至ViewGroup时,调用dispatchTouchEvent()进行分发处理:

1.检查送否应该对事件进行拦截:onInterceptTouchEvent(),若为true,跳过2步骤;
2.将事件依次分发给子View,若事件被某个View消费了,将不再继续分发;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

【Android 详细知识点思维脑图(技能树)】

我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

详细整理在GitHub可以见;

Android架构视频+BAT面试专题PDF+学习笔记

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

师还是比较缺少的**,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-M3Us8n40-1710817776053)]

详细整理在GitHub可以见;

Android架构视频+BAT面试专题PDF+学习笔记

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

最后,赠与大家一句话,共勉!

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值