SEO外包平台,我们为您提供专业的企业网站SEO整站优化外包服务 SEO设置

SEO外包平台

专注于企业网站SEO整站优化外包服务

Android事件传递分析-传递日志分析

作者:jcmp      发布时间:2021-05-06      浏览量:0
事件传递在android中是最基础的知识

事件传递在android中是最基础的知识,但也是最繁琐复杂的知识,在5.0后传递变的更加复杂,网上有很多文章分析事件分发,但是一开始就是在源码里面进行各种讲解这让整个事件传递消费事件的宏观性不强,本文章从最基础的日志开始分析传递事件,如果有耐心把文章认真分析完,我相信事件传递你已经了解了一半了!后面再配合源码进行理解,相信能做到更好!

一、方法解释

二、自定义组件

为了更好的解释传递与消费的过程,我定义了两个容器两个view,分别是ViewGroupA,ViewGroupB , viewC ,ViewD,并分别重写他们的onInterceptTouchEvent,dispatchTouchEvent,onTouchEvent。

代码如下:

public class ViewGroupA extends LinearLayout { public ViewGroupA(Context context) { super(context); } public ViewGroupA(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.onInterceptTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=" + result); return result; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.dispatchTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent return super.dispatchTouchEvent(ev)= " + result); return result; } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.onTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent return super.onTouchEvent(ev)=" + result); return result; }}

public class ViewGroupB extends LinearLayout { public ViewGroupB(Context context) { super(context); } public ViewGroupB(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.onInterceptTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=" + result); return result; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.dispatchTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent return super.dispatchTouchEvent(ev)= " + result); return result; } @Override public boolean onTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.onTouchEvent(ev); //boolean result = true; Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent return super.onTouchEvent(ev)=" + result); return result; }}

public class ViewC extends View { public ViewC(Context context) { super(context); } public ViewC(Context context, AttributeSet attrs) { super(context, attrs); } public ViewC(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.dispatchTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent return super.dispatchTouchEvent(ev)= " + result); return result; } @Override public boolean onTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.onTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent return super.onTouchEvent(ev)=" + result); return result; }}

public class ViewD extends View { public ViewD(Context context) { super(context); } public ViewD(Context context, AttributeSet attrs) { super(context, attrs); } public ViewD(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.dispatchTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent return super.dispatchTouchEvent(ev)= " + result); return result; } @Override public boolean onTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = super.onTouchEvent(ev); Log.d(LOG_ID, this.getClass().getSimpleName() + " onTouchEvent return super.onTouchEvent(ev)=" + result); return result; }}

工具方法:

public class ViewUtils { public static final int ACTION_DOWN = 0; /** * Constant for {@link #getActionMasked}: A pressed gesture has finished, the * motion contains the final release location as well as any intermediate * points since the last down or move event. */ public static final int ACTION_UP = 1; /** * Constant for {@link #getActionMasked}: A change has happened during a * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}). * The motion contains the most recent point, as well as any intermediate * points since the last down or move event. */ public static final int ACTION_MOVE = 2; /** * Constant for {@link #getActionMasked}: The current gesture has been aborted. * You will not receive any more points in it. You should treat this as * an up event, but not perform any action that you normally would. */ public static final int ACTION_CANCEL = 3; static String actionToString(int i) { switch (i) { case 0: return "ACTION_DOWN"; case 1: return "ACTION_UP"; case 2: return "ACTION_MOVE"; case 3: return "ACTION_CANCEL"; default: return "未知"; } }}

三、布局搭建

图解:

四、日志分析

五、不手动消耗事件不手动拦截事件的日志

通过上面的布局完成后,我们什么也不做,直接点击viewC控件日志如下:

D/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_DOWN D/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_DOWN D/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false D/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_DOWN D/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_DOWN D/点击事件: ViewGroupB onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false D/点击事件: ViewC dispatchTouchEvent -> ACTION_DOWN D/点击事件: ViewC onTouchEvent -> ACTION_DOWN D/点击事件: ViewC onTouchEvent return super.onTouchEvent(ev)=false D/点击事件: ViewC dispatchTouchEvent return super.dispatchTouchEvent(ev)= false D/点击事件: ViewGroupB onTouchEvent -> ACTION_DOWN D/点击事件: ViewGroupB onTouchEvent return super.onTouchEvent(ev)=false D/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= false D/点击事件: ViewGroupA onTouchEvent -> ACTION_DOWN D/点击事件: ViewGroupA onTouchEvent return super.onTouchEvent(ev)=false D/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= false。

点击viewC后,因为viewC与viewD是在同一个容器中,点击的坐标区域并不涉及到viewD,所以日志中并没有viewD的信息(下文相同)。

六、分析

点击viewC后:

七、手动消耗事件不手动拦截事件的日志

我们在代码中编写这样的一句代码

viewC.setOnClickListener { toast("我是C消费了此事件") }

同样点击viewC后得到日志如下:

D/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewC dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewC onTouchEvent -> ACTION_DOWND/点击事件: ViewC onTouchEvent return super.onTouchEvent(ev)=trueD/点击事件: ViewC dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_UPD/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_UPD/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_UPD/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_UPD/点击事件: ViewGroupB onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewC dispatchTouchEvent -> ACTION_UPD/点击事件: ViewC onTouchEvent -> ACTION_UPD/点击事件: ViewC onTouchEvent return super.onTouchEvent(ev)=trueD/点击事件: ViewC dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= true。

八、分析

这个时候可以看到事件的传递也是跟上面那种情况一样的,唯一不同的地方就是viewC中的onTouchEvent()方法返回的是一个true,这里也很明显,我们前面加了点击事件的处理,这里其实就是做了一个事件消耗,并且消耗完了这个事件后dispatchTouchEvent()也返回一个true来通知他的父容器我已经消耗了这个事件了,一直回调到最顶层view,因为这里处理了ACTION_DOWN事件,所以会出现ACTION_UP事件也需要处理,处理的方式跟ACTION_DOWN是一样的!

九、不手动消耗事件手动拦截事件的日志

我们做一个拦截实验,我们修改viewgroupB中onInterceptTouchEvent()的代码,我们手动给他返回一个true:

@Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = true; Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=" + result); return true; }

同样点击viewC后得到日志如下:

D/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=trueD/点击事件: ViewGroupB onTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onTouchEvent return super.onTouchEvent(ev)=falseD/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= falseD/点击事件: ViewGroupA onTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onTouchEvent return super.onTouchEvent(ev)=falseD/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= false。

十、分析

从直观的感受似乎日志里面只有viewgroupA与viewgroupB的日志,是的没错。

十一、手动消耗事件手动拦截事件的日志

我们添加消费事件的代码:

viewGroupB.setOnClickListener { toast("我是B消费了此事件") }

同时拦截事件让viewGroupB的onInterceptTouchEvent返回为true。

public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent -> " + ViewUtils.actionToString(ev.getAction())); boolean result = true; Log.d(LOG_ID, this.getClass().getSimpleName() + " onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=" + result); return true; }

这个时候我们点击viewC按钮日志如下:

D/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_DOWND/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=trueD/点击事件: ViewGroupB onTouchEvent -> ACTION_DOWND/点击事件: ViewGroupB onTouchEvent return super.onTouchEvent(ev)=trueD/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_UPD/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_UPD/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=falseD/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_UPD/点击事件: ViewGroupB onTouchEvent -> ACTION_UPD/点击事件: ViewGroupB onTouchEvent return super.onTouchEvent(ev)=trueD/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= trueD/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= true。

十二、分析

到了这里大家应该都能猜到会发生什么现象了吧

十三、总结

如果认真的看完这里的日志,相信对事件的消耗分发了解了一半了,至于为什么点击的时候没得viewD的事,还有很多不明白的地方,我们会下次继续用源码进行分析!

十四、提醒

需要源码的朋友可以发送请求到邮箱 imkobedroid@gmail.com 文章与代码有待改进!希望可以交流。