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

  • 外部拦截法:在viewGroup中判断滑动的角度,如果符合自身滑动方向消费则拦截事件

  • 内部拦截法:在内部view中判断滑动的角度,如果是符合自身滑动方向则继续消费事件,否则请求外部viewGroup拦截事件处理

从实现的复杂度来看,外部拦截法会更加优秀,不需要里外view去配合,只需要viewGroup自身做好事件拦截处理即可。两者的区别就在于主动权在谁的手上。如果view需要做更多的判断可以采用内部拦截法,而一般情况下采用外部拦截法会更加简单。

接下来思考一下这两种方法的代码实现。


外部拦截法中,重点在于是否拦截事件,那么我们的重心就放在了 onInterceptTouchEvent 方法中。在这个方法中计算滑动角度并判断是否要进行拦截。这里以ScrollView为例子(外部是垂直滑动,内部是水平滑动),代码如下:

public class MyScrollView extends ScrollView {

// 记录上一次事件的坐标

float lastX = 0;

float lastY = 0;

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

int actionMasked = ev.getActionMasked();

// 不能拦截down事件,否则子view永远无法获取到事件

// 不能拦截up事件,否则子view的点击事件无法被触发

if (actionMasked == MotionEvent.ACTION_DOWN || actionMasked == MotionEvent.ACTION_UP){

lastX = ev.getX();

lastY = ev.getY();

return false;

}

// 获取斜率并判断

float x = ev.getX();

float y = ev.getY();

return Math.abs(lastX - x) < Math.abs(lastY - y);

}

}

代码的实现思路很简单,记录两次触控点的位置,然后计算出斜率来判断是垂直还是水平滑动。代码中有个需要注意的点:viewGroup不能拦截up事件和down事件。如果拦截了down事件那么子view将永远接收不到事件信息;如果拦截了up事件那么子view将永远无法触发点击事件。

上面的代码是事件分发的核心代码,更加具体的代码还需要根据实际需求去完善细节,但整体的逻辑是不变的。


内部拦截法的思路和外部拦截的思路很像,只是判断的位置放到了内部view中。内部拦截法意味着内部view必须要有控制事件流走向的能力,才能对事件进行处理。这里就运用到了内部view一个重要的方法: requestDisallowInterceptTouchEvent 。

这个方法可以强制外层viewGroup不拦截事件。因此,我们可以让viewGroup默认拦截除了down事件以外的所有事件。当子view需要处理事件时,只需要调用此方法即可获取事件;而当想要把事件交给viewGroup处理时,那么只需要取消这个标志,外层viewGroup就会拦截所有事件。从而达到内部view控制事件流走向的目的。

代码实现需要分两步走,首先是设置外部viewGroup拦截除了down事件以外的所有事件(这里用viewPager和ListView来进行代码演示):

public class MyViewPager extends ViewPager {

public boolean onInterceptTouchEvent(MotionEvent ev) {

if (ev.getActionMasked()==MotionEvent.ACTION_DOWN){

return false;

}

return true;

}

}

接下来需要重写内部view的dispatchTouchEvent方法:

public class MyListView extends ListView {

float lastX = 0;

float lastY = 0;

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

int actionMarked = ev.getActionMasked();

switch (actionMarked){

// down事件,必须请求不拦截,否则拿不到move事件无法进行判断

case MotionEvent.ACTION_DOWN:{

requestDisallowInterceptTouchEvent(true);

break;

}

// move事件,进行判断是否处理事件

case MotionEvent.ACTION_MOVE:{

float x = ev.getX();

float y = ev.getY();

// 如果滑动角度大于90度自己处理事件

if (Math.abs(lastY-y)<Math.abs(lastX-x)){

requestDisallowInterceptTouchEvent(false);

}

break;

}

default:break;

}

// 保存本次触控点的坐标

lastX = ev.getX();

lastY = ev.getY();

// 调用ListView的dispatchTouchEvent方法来处理事件

return super.dispatchTouchEvent(ev);

}

}

两种方法的代码思路基本一致,但是内部拦截法会更加复杂一点,所以在一般的情况下,还是使用外部拦截法较好。

到这里已经解决了情况一的滑动冲突解决方案,接下来看看情况二的滑动冲突如何解决。

情况二

第二种情况是里外容器的滑动方向是一致的,这种情况的主流解决方法有两种,一种是外容器先滑动,外容器滑动到边界之后再滑动内部view,例如京东app(注意向下滑动时的情况):

第二种情况的内部view先滑动,等内部view滑动到边界之后再滑动外部viewGroup,例如饿了么app(注意向下滑动时的情况):

这两种方案没有孰好孰坏,而是需要根据具体的业务需求来确定具体的解决方案。下面就上述的第二种方案展开分析,第一种方案类同。

首先分析一下具体的效果:外层viewGroup与内层view的滑动方向是一致的,都是垂直滑动或水平滑动;向上滑动时,先滑动viewGroup到顶部,再滑动内部view;向下滑动时,先滑动内部view到顶部后再滑动外层viewGroup。

这里我们采用外部拦截法来实现。首先我们先确定好我们的布局:

最外层是一个ScrollView,内部首先是一个LinearLayout,因为ScrollView只能有一个view。内部顶部是一个LinearLayout可以放置头部布局,下面是一个ListView。现在需要确定ScrollView的拦截规则:

  1. 当ScrollView没有滑动到底部时,直接给ScrollView处理

  2. 当ScrollView滑动到底部时:

  • 如果LinearLayout没有滑动到顶部,则交给ListView处理

  • 如果LinearLayout滑动到顶部:

  • 如果是向上滑动则交给listView处理

  • 如果是向下滑动则交给ScrollView处理

接下来就可以确定我们的代码了:

public class MyScrollView extends ScrollView {

float lastY = 0;

boolean isScrollToBottom = false;

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

boolean intercept = false;

int actionMarked = ev.getActionMasked();

switch (actionMarked){

case MotionEvent.ACTION_DOWN:

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_CANCEL:{

// 这三种事件默认不拦截,必须给子view处理

break;

}

case MotionEvent.ACTION_MOVE:{

LinearLayout layout = (LinearLayout) getChildAt(0);

ListView listView = (ListView)layout.getChildAt(1);

// 如果没有滑动到底部,由ScrollView处理,进行拦截

if (!isScrollToBottom){

intercept = true;

// 如果滑动到底部且listView还没滑动到顶部,不拦截

}else if (!ifTop(listView)){

intercept = false;

}else{

// 否则判断是否是向下滑

intercept = ev.getY() > lastY;

}

break;

}

default:break;

}

// 最后记录位置信息

lastY = ev.getY();

// 调用父类的拦截方法,ScrollView需要做一些处理,不然可能会造成无法滑动

super.onInterceptTouchEvent(ev);

return intercept;

}

}

代码中我还增加了如果listView下面有view的情况,判断是否滑动到底部。判断listView滑动情况和scrollView滑动情况的代码如下:

{

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

// 设置滑动监听

setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {

ViewGroup viewGroup = (ViewGroup)v;

isScrollToBottom = v.getHeight() + scrollY >= viewGroup.getChildAt(0).getHeight();

});

}

}

// 判断listView是否到达顶部

private boolean ifTop(ListView listView){

if (listView.getFirstVisiblePosition()==0){

View view = listView.getChildAt(0);

return view != null && view.getTop() >= 0;

}

return false;

}

最终的实现效果如下图:

这样就简单地解决一个滑动冲突了。但是要注意的是,在实际问题中,往往有更加复杂的细节需要处理。而上述只是把解决滑动冲突的一个思想分析了一下,具体到业务上,还需要去细心打磨代码才行。有兴趣可以去看看NeatedScrollView是如何解决滑动冲突的源码。

总结

事件分发作为Android的基础知识储备可谓是非常重要。不能说学了事件分发,就可以直接一飞冲天。而是掌握了事件分发之后,面对一些具体的需求,就有了一定的思路去处理。或者在了解一些框架的源码的时候,懂得他这些代码是什么意思。

学习事件分发的过程中,深入研究了很多的源码,有一些小伙伴觉得没必要。实际开发中也就用到那三个主要的方法,了解一个主要的流程就足够了。我想说:确实是这样;但没有研究背后的原理,就只能知其然而不知其所以然。当遇到一些异常的情况时,就无法从源码的角度去分析结果的bug。学习源码的过程中,也是与设计android系统的作者的一种交流。倘若现在没有事件分发机制,那么我该如何去解决触摸信息的分发问题?学习的过程就是在思考android系统作者给出的解决方案。而掌握原理之后,对于事件分发的问题,稍加思考和分析,也就手到擒来了。正所谓:

只有打败10级的敌人,才能掌控9级的敌人。

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

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

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

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

总结

Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

上面分享的字节跳动公司2021年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

Android学习PDF+学习视频+面试文档+知识点笔记

【Android高级架构视频学习资源】

是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频**
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-RRexyGbx-1710817740721)]

总结

Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

上面分享的字节跳动公司2021年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
[外链图片转存中…(img-xPgV26J4-1710817740722)]

Android学习PDF+学习视频+面试文档+知识点笔记

【Android高级架构视频学习资源】

Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值