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


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 ")

}

}

}


手势识别

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

在 Android 开发当中,几乎所有的事件都会与用户进行交互,而我们用得的最多的就是手势了。

我们知道当我们触摸屏幕的时候,会产生很多事件,比如 down,move,up, fling 事件等等。一些简单的处理,我们可以直接重写 View 的 onTouchEvent 方法,根据 View 的 MotionEvent 事件进行处理。

而 Google 为了方便开发者方便接入,提供了几个默认处理类,那就是 GestureDetector 和 ScaleGestureDetector。

GestureDetector这个类对外提供了两个接口和一个外部类 。 接口:OnGestureListener,OnDoubleTapListener

内部类:SimpleOnGestureListener,同时实现了 OnGestureListener,OnDoubleTapListener 接口,如果只想使用接口里面的某个方法,可以直接使用它,方便快捷。

讲解之前,我们向来看一下怎么使用

GestureDetector(Context context, GestureDetector.OnGestureListener listener)

GestureDetector 基本使用


第一步,初始化 GestureDetector 对象

mDetector = GestureDetectorCompat(this, MyGestureListener())

可以看到有两个参数,第一个参数 context,第二个参数 OnGestureListener,我们可以直接实现 OnGestureListener 接口,也可以直接使用 GestureDetector.SimpleOnGestureListener

private class MyGestureListener : GestureDetector.OnGestureListener {

private val TAG = “GestureDemoActivity”

override fun onShowPress(e: MotionEvent?) {

Log.d(TAG, “onShowPress: e is $e”)

}

override fun onSingleTapUp(e: MotionEvent?): Boolean {

Log.d(TAG, “onSingleTapUp: e is $e”)

return false

}

override fun onDown(event: MotionEvent): Boolean {

Log.d(TAG, “onDown: $event”)

return true

}

override fun onFling(

event1: MotionEvent,

event2: MotionEvent,

velocityX: Float,

velocityY: Float

): Boolean {

Log.d(TAG, “onFling: $event1 $event2”)

return false

}

override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {

Log.d(TAG, "onScroll: distanceX is $distanceX,distanceY is $distanceY ")

return false

}

override fun onLongPress(e: MotionEvent?) {

Log.d(TAG, “onLongPress: e is $e”)

}

}

第二步:设置双击监听

// 设置双击监听

mDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener {

override fun onDoubleTap(e: MotionEvent?): Boolean {

Log.d(TAG, “onDoubleTap: e is e”)

return false

最后

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

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
厂,18年进入阿里一直到现在。**

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

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

[外链图片转存中…(img-GVw4Itad-1715443489706)]

[外链图片转存中…(img-kPAGpeOW-1715443489708)]

[外链图片转存中…(img-XFOFP3Et-1715443489709)]

[外链图片转存中…(img-CmDfLYRu-1715443489710)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值