思路梳理
在深入分析事件分发源码之前,需要先弄清楚2个概念。
ViewGroup
ViewGroup 是一组 View 的组合,在其内部有可能包含多个子 View,当手指触摸屏幕上时,手指所在的区域既能在ViewGroup显示范围内,也可能在其内部 View 控件上。因此它内部的事件分发的重心是处理当前 ViewGroup 和子 View 之间的逻辑关系:
-
1.当前 ViewGroup 是否需要拦截Touch事件;
-
2.是否需要将Touch事件继续分发给子 View;
-
3.如何将Touch事件分发给子 View。
View
View 是一个单纯的控件,不能再被细分,内部也并不会存在子 View,所以它的事件分发的重点在于当前 View 如何去处理 Touch 事件,并根据相应的手势逻辑进行一些列的效果展示(比如滑动,放大,点击,长按等)。
-
1.是否存在 TouchListener;
-
2.是否自己接收处理 touch 事件(主要逻辑在 onTouchEvent 方法中)。
涉及事件分发的方法
方法的简单用途解析
我们可以发现这三个方法的返回值都为boolean类型,其实它们就是通过返回值来决定下一步的传递处理方向。
1、dispatchTouchEvent() ——用来分发事件所用
该方法会将Touch事件**「自上而下」依次分发到子元素中,直到被终止或者到达View层,该方法也是采用一种「隧道方式来分发」。在其中会调用onInterceptTouchEvent()和onTouchEvent(),「一般不会重写」**。
-
返回false则不拦截继续往下分发;
-
返回true则拦截住该事件不在向下层元素分发;
在dispatchTouchEvent()方法中默认返回false。
2、onInterceptTouchEvent() ——用来拦截事件所用
-
返回false不拦截事件,Touch事件就会往下传递给其子View。
-
返回true,该事件将会被拦截,并且被当前ViewGroup处理,调用ViewGroup的onTouchEvent()方法。
3、onTouchEvent() ——用来处理事件
-
返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View)。
-
返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理。
拥有上述方法的类
「注意:」 需要特别注意一点就是ViewGroup中额外拥有onInterceptTouchEvent()方法,其他两个方法为这三种类所共同拥有。
事件分发流程
单个事件触发后,事件分发流程:Activity①>ViewGroup②>View③,如下图:
即要想充分理解Android分发机制,本质上是要理解:
-
Activity对点击事件的分发机制
-
ViewGroup对点击事件的分发机制
-
View对点击事件的分发机制
从U型图中可以发现,由父组件不断向子组件分发,若子组件能够处理,则立刻返回。若子组件都不处理,那传递到底层的子组件,再返回回来。「整个View之间的事件分发,实质上就是一个大的递归函数」。
实例
==
下面咱们写一个简单实例来更好的理解这个大U型图,效果图:
创建实例
创建MyViewGroup继承ViewGroup
重写dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()方法
public class MyViewGroup extends RelativeLayout {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MLog.logEvent(“MyViewGroup.dispatchTouchEvent:”,ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
MLog.logEvent(“MyViewGroup.onInterceptTouchEvent:”,ev);
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
MLog.logEvent(“MyViewGroup.onTouchEvent自行处理:”,event);
return super.onTouchEvent(event);
}
}
创建MyView继承View
重写dispatchTouchEvent()、onTouchEvent()方法
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MLog.logEvent(“MyView.dispatchTouchEvent:”,ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
MLog.logEvent(“MyView.onTouchEvent:”,event);
return super.onTouchEvent(event);
}
}
创建TouchActivity继承Activity
重写dispatchTouchEvent()、onTouchEvent()方法
public class TouchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MLog.logEvent(“TouchActivity.dispatchTouchEvent:”,ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
MLog.logEvent(“TouchActivity.onTouchEvent:”,event);
return super.onTouchEvent(event);
}
}
创建布局文件
添加控件MyViewGroup和MyView
<?xml version="1.0" encoding="utf-8"?><com.scc.demo.view.MyViewGroup xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=“@+id/mtrg_touch”
android:layout_width=“match_parent”
android:layout_height=“300dp”
android:background=“@color/color_FF773D”>
<com.scc.demo.view.MyView
android:id=“@+id/mtv_onclick”
android:layout_width=“120dp”
android:layout_height=“60dp”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“40dp”
android:background=“@color/color_188FFF”/>
</com.scc.demo.view.MyViewGroup>
MLog.logEvent()
方便查看对应事件
public static void logEvent(String msg, MotionEvent event) {
String motionEvent = “”;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN://在屏幕按下时(所有事件的开始)
motionEvent=“DOWN”;
break;
case MotionEvent.ACTION_UP://在屏幕抬起时(与DOWN对应)
motionEvent=“UP”;
break;
case MotionEvent.ACTION_MOVE://在屏幕上滑动时
motionEvent=“MOVE”;
break;
case MotionEvent.ACTION_CANCEL://滑动超出控件边界时
motionEvent=“CANCEL”;
break;
}
Log.e(“SccEvent”, msg + motionEvent);
}
点击页面,看效果
点击Activity(白色区域)
运行结果
E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:UP
E/SccEvent: TouchActivity.onTouchEvent:UP
点击ViewGroup(黄色区域)
运行结果
E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
E/SccEvent: MyViewGroup.onTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:UP
E/SccEvent: TouchActivity.onTouchEvent:UP
点击View(蓝色区域)
运行结果
E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
E/SccEvent: MyView.dispatchTouchEvent:DOWN
E/SccEvent: MyView.onTouchEvent:DOWN
E/SccEvent: MyViewGroup.onTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:UP
E/SccEvent: TouchActivity.onTouchEvent:UP
结果分析
重叠区域越多触发事件越多。这就用到了事件分发。点击蓝色区域,走完了一个大U分发,没有人拦截和处理。
看看上面的日志你会发现按下事件走了那么多方法,为什么滑动和抬起仅调用Activity中的方法?
原因就是:MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。可参阅文章后面的**「为什么 DOWN 事件特殊」**
本次将所有Touch事件打印,后续仅打印DOWN事件。
事件分发和处理
Activity处理和分发
Activity处理
1.将MyViewGroup.dispatchTouchEvent()返回修改为false
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MLog.logEvent(“MyViewGroup.dispatchTouchEvent:”,ev);
return false;
}
运行结果
E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.SccEvent就在这里玩
2.MyViewGroup仅拦截不处理,也会交给Activity.onTouchEvent处理
3.向下分发的事件ViewGroup和View都不处理,最后还是交给Activity.onTouchEvent处理(如**「上面点击蓝色区域结果分析」**)
Activity分发
Actvitiy是事件分发的顶层,dispatchTouchEvent方法返回true不做分发,事件结束。返回false或者super.dispatchTouchEvent(ev)则向下分发事件。向下分发就没必要处理(重写)dispatchTouchEvent()
ViewGroup拦截处理和分发
ViewGroup拦截处理
确保Activity做好分发后(即不对dispatchTouchEvent做修改),「修改MyViewGroup.onInterceptTouchEvent()和MyViewGroup.onTouchEvent()方法」,ViewGroup的重点在于事件拦截(onInterceptTouchEvent),所以咱重写拦截事件和处理事件的两个方法即可。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MLog.logEvent(“MyViewGroup.dispatchTouchEvent:”,ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
MLog.logEvent(“MyViewGroup.onInterceptTouchEvent:”,ev);
return true;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
写在最后
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从哪里入手去学习,对此我整理了一些资料
如果你熟练掌握以下列出的知识点,相信将会大大增加你通过前两轮技术面试的几率!这些内容都供大家参考,互相学习。
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包,最后觉得有帮助、有需要的朋友可以点个赞
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
写在最后
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从哪里入手去学习,对此我整理了一些资料
如果你熟练掌握以下列出的知识点,相信将会大大增加你通过前两轮技术面试的几率!这些内容都供大家参考,互相学习。
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包,最后觉得有帮助、有需要的朋友可以点个赞
[外链图片转存中…(img-GRqfg6uP-1712375407255)]
[外链图片转存中…(img-t6j32Z0P-1712375407255)]
[外链图片转存中…(img-3876ssms-1712375407255)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!