Android面试老生常谈的 View 事件分发机制,看这一篇就够了!

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

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

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

从 ACTION_DOWN 开始到 ACTION_UP/ACTION_CANCEL 结束我们称为一个事件序列

正常情况下,无论你手指在屏幕上有多么骚的操作,最终呈现在 MotionEvent 上来讲无外乎下面 3 种 case。

  1. 点击后抬起,也就是单击操作:ACTION_DOWN -> ACTION_UP

  2. 点击后再风骚的滑动一段距离,再抬起:ACTION_DOWN -> ACTION_MOVE -> … -> ACTION_MOVE -> ACTION_UP

  3. 某些情况下,我们可能会没有收到 ACTION_UP 事件,是收到 ACTION_CANCEL 事件。

ACTION_CANCEL 一般是指 ChildView 原先拥有事件处理权,后面由于某些原因,该处理权需要交回给上层去处理,ChildView便会收到 ACTION_CANCEL 事件。对于一些复位或者重置操作,我们应该在 ACTION_UP 和 ACTION_CANCEL 里面同时进行处理

代码逻辑上是:上层判断之前交给ChildView的事件处理权需要收回来了,便会做事件的拦截处理,拦截时给ChildView发一个ACTION_CANCEL事件

几个主要方法


我们知道,View 的事件分发机制主要涉及到以下几个方法

  • dispatchTouchEvent ,这个方法主要是用来分发事件的

  • onInterceptTouchEvent,这个方法主要是用来拦截事件的(需要注意的是 ViewGroup 才有这个方法,View 没有 onInterceptTouchEvent 这个方法)

  • onTouchEvent 这个方法主要是用来处理事件的

  • requestDisallowInterceptTouchEvent(true),这个方法能够影响父View是否拦截事件,true 表示父 View 不拦截事件,false 表示父 View 拦截事件

我们先来看一张图。

以下内容参考图解 Android 事件分发机制这一篇博客

  • 仔细看的话,图分为3层,从上往下依次是Activity、ViewGroup、View

  • 事件从左上角那个白色箭头开始,由 Activity 的 dispatchTouchEvent 进行分发

  • 箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。)

  • dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。

  • 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。

当触摸事件发生时,首先 Activity 将 TouchEvent 传递给最顶层的 View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,

如果dispatchTouchEvent返回true 消费事件,事件终结。

如果dispatchTouchEvent返回 false ,则回传给父View的onTouchEvent事件处理;

如果dispatchTouchEvent返回super的话,默认会调用自己的onInterceptTouchEvent方法。

  • 默认的情况下onInterceptTouchEvent回调用super方法,super方法默认返回false,所以会交给子View的onDispatchTouchEvent方法处理

  • 如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,

  • 如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。

关于更多详细分析,请查看原博客图解 Android 事件分发机制,真心推荐,写得很好。


View 滑动事件冲突

=========================================================================

在开发当中,View 的滑动冲突时经常遇到的,比如 ViewPager 嵌套 ViewPager,ScrollView 嵌套 ViewPager。下面让我们一起来看看怎么解决。

常见的三种情况


第一种情况,滑动方向不同

第二种情况,滑动方向相同

第三种情况,上述两种情况的嵌套

解决思路


看了上面三种情况,我们知道他们的共同特点是父View 和子View都想争着响应我们的触摸事件,但遗憾的是我们的触摸事件 同一时刻只能被某一个View或者ViewGroup拦截消费,所以就产生了滑动冲突。

那既然同一时刻只能由某一个 View 或者 ViewGroup 消费拦截,那我们就只需要 决定在某个时刻由这个 View 或者 ViewGroup 拦截事件,另外的 某个时刻由 另外一个 View 或者 ViewGroup 拦截事件,不就 OK了吗?

综上,正如 在 《Android开发艺术》 一书提出的,总共 有两种解决方案

以下解决思路来自于 《Android开发艺术》 书籍

下面的两种方法针对第一种情况(滑动方向不同),父View是上下滑动,子View是左右滑动的情况。

外部解决法


从父View着手,重写onInterceptTouchEvent方法,在父View需要拦截的时候拦截,不要的时候返回false,为代码大概 如下

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

final float x = ev.getX();

final float y = ev.getY();

final int action = ev.getAction();

switch (action) {

case MotionEvent.ACTION_DOWN:

mDownPosX = x;

mDownPosY = y;

break;

case MotionEvent.ACTION_MOVE:

final float deltaX = Math.abs(x - mDownPosX);

final float deltaY = Math.abs(y - mDownPosY);

// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截

if (deltaX > deltaY) {

return false;

}

}

return super.onInterceptTouchEvent(ev);

}

内部解决法


从子View着手,父View先不要拦截任何事件,所有的事件传递给 子View,如果子View需要此事件就消费掉,不需要此事件的话就交给 父View处理。

实现思路 如下,重写子 View的dispatchTouchEvent方法,在Action_down 动作中通过方法 requestDisallowInterceptTouchEvent(true) 先请求 父 View不要拦截事件,这样保证子 View 能够接受到 Action_move 事件,再在 Action_move 动作中根据自己的逻辑是否要拦截事件,不需要拦截事件的话再交给 父 View 处理。

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

int x = (int) ev.getRawX();

int y = (int) ev.getRawY();

int dealtX = 0;

int dealtY = 0;

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

dealtX = 0;

dealtY = 0;

// 保证子View能够接收到Action_move事件

getParent().requestDisallowInterceptTouchEvent(true);

break;

case MotionEvent.ACTION_MOVE:

dealtX += Math.abs(x - lastX);

dealtY += Math.abs(y - lastY);

Log.i(TAG, “dealtX:=” + dealtX);

Log.i(TAG, “dealtY:=” + dealtY);

// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截

if (dealtX >= dealtY) {

getParent().requestDisallowInterceptTouchEvent(true);

} else {

getParent().requestDisallowInterceptTouchEvent(false);

}

lastX = x;

lastY = y;

break;

case MotionEvent.ACTION_CANCEL:

break;

case MotionEvent.ACTION_UP:

break;

}

return super.dispatchTouchEvent(ev);

}

更多细节,可以查看我的这篇文章,里面有详细介绍哦 ViewPager,ScrollView 嵌套ViewPager滑动冲突解决


View 双击,多击事件是怎么实现的

================================================================================

实现之前,我们首先来阐述一下思路,怎样实现双击事件,正所谓,授人以鱼不如授人以渔。

单击:用户点击一次之后,一段时间之内不再点击

双击;用户点击一次之后,一段时间之内再次点击

实现思路

  1. 我们监听 onTouch 事件,在 ACTION_DOWN 的时候,点击次数 clickCount +1;

  2. 同时,在 ACTION_DOWN 的时候,延时一段时间,执行相应的 Runnable 任务,这里我们用 handler 的 postDelayed 实现

  3. 在延时任务执行的时候,我们根据点击的次数,进行单击或者多级的回调,最后,记得重置点击次数,以及移除延时任务

open class MyDoubleTouchListener(private val myClickCallBack: MyClickCallBack) : OnTouchListener {

private var clickCount = 0 //记录连续点击次数

private val handler: Handler = Handler()

interface MyClickCallBack {

fun oneClick() //点击一次的回调

fun doubleClick() //连续点击两次的回调

}

override fun onTouch(v: View, event: MotionEvent): Boolean {

if (event.action == MotionEvent.ACTION_DOWN) {

clickCount++

handler.postDelayed({

if (clickCount == 1) {

myClickCallBack.oneClick()

} else if (clickCount == 2) {

myClickCallBack.doubleClick()

}

handler.removeCallbacksAndMessages(null)

//清空handler延时,并防内存泄漏

clickCount = 0 //计数清零

}, timeout.toLong()) //延时timeout后执行run方法中的代码

}

return false //让点击事件继续传播,方便再给View添加其他事件监听

}

companion object {

private const val TAG = “MyClickListener”

private val timeout = ViewConfiguration.getDoubleTapTimeout() //双击间四百毫秒延时

init {

Log.i(TAG, "timeout is $timeout ")

}

}

}

三击事件


三级事件呢,其实也很简单,我们直接判断在指定时间间隔内点击的次数即可

open class MyMultiTouchListener(private val myClickCallBack: MyClickCallBack) : OnTouchListener {

private var clickCount = 0 //记录连续点击次数

private val handler: Handler = Handler()

interface MyClickCallBack {

fun oneClick() //点击一次的回调

fun doubleClick() //连续点击两次的回调

fun threeClick() // 连续点击三次的回调

}

override fun onTouch(v: View, event: MotionEvent): Boolean {

if (event.action == MotionEvent.ACTION_DOWN) {

clickCount++

handler.postDelayed({

if (clickCount == 1) {

myClickCallBack.oneClick()

} else if (clickCount == 2) {

myClickCallBack.doubleClick()

} else if (clickCount == 3) {

myClickCallBack.threeClick()

}

handler.removeCallbacksAndMessages(null)

//清空handler延时,并防内存泄漏

clickCount = 0 //计数清零

}, timeout.toLong()) //延时timeout后执行run方法中的代码

}

return false //让点击事件继续传播,方便再给View添加其他事件监听

}

companion object {

private const val TAG = “MyClickListener”

private val timeout = 600 //双击间四百毫秒延时

init {

Log.i(TAG, "timeout is $timeout ")

}

}

}

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-3aef4l0z-1713047180286)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-yO0Uw68I-1713047180286)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值