08.Android之View事件问题

6次阅读

共计 11483 个字符,预计需要花费 29 分钟才能阅读完成。

目录介绍

8.0.0.1 简述 Android 的事件分发机制?dispatchTouchEvent 方法的作用是什么?说下 View 和 ViewGroup 分发事件?
8.0.0.2 onInterceptTouchEvent 方法作用是什么?onTouchEvent 的方法的作用是什么?
8.0.0.4 滑动冲突有哪些场景?滑动冲突处理原则是什么?滑动冲突解决办法有哪些?分别是如何解决的?
8.0.0.5 onTouch()、onTouchEvent()和 onClick()关系是怎样的,哪一个先执行?如果设置了 onClickListener, 但是 onClick()没有调用,可能产生的原因?
8.0.0.6 View 滑动有哪些方法?这些方法分别是如何实现滑动的?分别有什么优缺点?
8.0.0.7 事件的传递规则是什么?View 处理事件的优先级?点击事件传递过程遵循如下顺序?事件传递规则要点?
8.0.0.8 Scroller 的作用?Scroller 的要点有哪些?Scroller 的使用步骤?Scroller 工作原理?
8.0.0.9 Activity 事件分发的过程?Window 事件分发?DecorView 的事件分发?根 View 的事件分发?
8.0.1.0 GestureDetector 是干什么用的?GestureDetector 作用和注意点?有哪些常用的监听方法?
8.0.1.2 View 的滑动方式?如何让控件滚动到某一位置?scrollTo()和 scrollBy()的区别?Scroller 是什么?
8.0.1.4 谈一谈 View 的工作原理,执行流程,MeasureSpec 是什么?有什么作用?
8.0.1.6 SurfaceView 和 View 的区别,说一下 SurfaceView 的工作原理,为何不会导致页面卡顿?

好消息

博客笔记大汇总【15 年 10 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 500 篇[近 100 万字],将会陆续发表到网上,转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

8.0.0.1 简述 Android 的事件分发机制?dispatchTouchEvent 方法的作用是什么?说下 View 和 ViewGroup 分发事件?

简述 Android 的事件分发机制?技术博客大总结

事件分发顺序:Activty->ViewGroup->View
主要方法:dispatchTouchEvent- 分发事件、onInterceptTouchEvent- 当前 View 是否拦截该事件、onTouchEvent- 处理事件
1. 父 View 调用 dispatchTouchEvent 开启事件分发。
2. 父 View 调用 onInterceptTouchEvent 判断是否拦截该事件,一旦拦截后该事件的后续事件 (如 DOWN 之后的 MOVE 和 UP) 都直接拦截,不会再进行判断。
3. 如果父 View 进行拦截,父 View 调用 onTouchEvent 进行处理。
4. 如果父 View 不进行拦截,会调用子 View 的 dispatchTouchEvent 进行事件的层层分发。

dispatchTouchEvent 方法的作用是什么?

用于进行事件的分发
只要事件传给当前 View,该方法一定会被调用
返回结果受到当前 View 的 onTouchEvent 和下级 View 的 dispatchTouchEvent 影响
表示是否消耗当前事件

ViewGroup 事件分发伪代码

View 事件分发伪代码

View 和 ViewGroup 在 dispatchTouchEvent 上的区别

ViewGroup 在 dispatchTouchEvent()中会进行事件的分发。
View 在 dispatchTouchEvent()中会对该事件进行处理。技术博客大总结

8.0.0.2 onInterceptTouchEvent 方法作用是什么?onTouchEvent 的方法的作用是什么?

onInterceptTouchEvent 方法作用是什么?

在 dispatchTouchEvent 的内部调用,用于判断是否拦截某个事件

View 和 ViewGroup 在 onInterceptTouchEvent 上的区别:

View 没有该方法,View 会处理所有收到的事件,但不一定会消耗该事件。
onInterceptTouchEvent 是 ViewGroup 中添加的方法,用于判断是否拦截该事件。

onTouchEvent 的方法的作用是什么?技术博客大总结

在 dispatchTouchEvent 的中调用,用于处理点击事件
返回结果表示是否消耗当前事件

8.0.0.4 滑动冲突有哪些场景?滑动冲突处理原则是什么?滑动冲突解决办法有哪些?分别是如何解决的?

滑动冲突有哪些场景?

内层和外层滑动方向不一致:一个垂直,一个水平。比如轮播图 ViewPager 和 ScrollView
内存和外层滑动方向一致:均垂直 or 水平。比如 scrollView 和 RecyclerView
前两者层层嵌套。比如 ScrollView 和 RecyclerView(recyclerView 中又嵌套 recyclerView)

滑动冲突处理原则

对于内外层滑动方向不同,只需要根据滑动方向来给相应控件拦截
对于内外层滑动方向相同,需要根据业务来进行事件拦截,规定何时让外部 View 拦截事件何时由内部 View 拦截事件。
前两者嵌套的情况,根据前两种原则层层处理即可。

滑动冲突解决办法有哪些?技术博客大总结

外部拦截法:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。具体方法:需要重写父容器的 onInterceptTouchEvent 方法,在内部做出相应的拦截。
内部拦截法:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。具体方法:需要配合 requestDisallowInterceptTouchEvent 方法。

外部拦截解决滑动冲突法

外部拦截法要点

父容器的 onInterceptTouchEvent 方法中处理
ACTION_DOWN 不拦截,一旦拦截会导致后续事件都直接交给父容器处理。
ACTION_MOVE 中根据情况进行拦截,拦截:return true,不拦截:return false(外部拦截核心)
ACTION_UP 不拦截,如果父控件拦截 UP,会导致子元素接收不到 UP 进一步会让 onClick 方法无法触发。此外 UP 拦截也没什么用。

onClick 方法生效的两个条件?

View 可以点击
接收到了 DOWN 和 UP 事件

外部拦截,自定义 ScrollView,这块可以看我的博客:

8.0.0.5 onTouch()、onTouchEvent()和 onClick()关系是怎样的,哪一个先执行?如果设置了 onClickListener, 但是 onClick()没有调用,可能产生的原因?

onTouch()、onTouchEvent()和 onClick()关系是怎样的,哪一个先执行?

onTouch->onTouchEvent->onClick

当一个 View 需要处理事件时,如果它设置了 OnTouchListener,那么 OnTouchListener 的 onTouch 方法会被回调。
这时事件如何处理还得看 onTouch 的返回值,如果返回 false,则当前 View 的 onTouchEvent 方法会被调用;如果返回 true,那么 onTouchEvent 方法将不会被调用。由此可见,给 View 设置的 onTouchListener,其优先级比 onTouchEvent 要高。
如果当前方法中设置了 onClickListener,那么它的 onClick 方法会被调用。可以看出,常用的 OnClickListener,其优先级别最低。

如果设置了 onClickListener, 但是 onClick()没有调用,可能产生的原因?技术博客大总结

父 View 拦截了事件,没有传递到当前 View
View 的 Enabled = false(setEnabled(false)): view 处于不可用状态,会直接返回。
View 的 Clickable = false(setClickablesetLongClickable(false)):view 不可以点击,不会执行 onClick
View 设置了 onTouchListener,且消耗了事件。会提前返回。
View 设置了 TouchDelegate,且消耗了事件。会提前返回。

8.0.0.6 View 滑动有哪些方法?这些方法分别是如何实现滑动的?分别有什么优缺点?

View 滑动有哪些方法?

layout:对 View 进行重新布局定位。在 onTouchEvent()方法中获得控件滑动前后的偏移。然后通过 layout 方法重新设置。
offsetLeftAndRight 和 offsetTopAndBottom: 系统提供上下 / 左右同时偏移的 API。onTouchEvent()中调用
LayoutParams: 更改自身布局参数
scrollTo/scrollBy: 本质是移动 View 的内容,需要通过父容器的该方法来滑动当前 View
Scroller: 平滑滑动,通过重载 computeScroll(),使用 scrollTo/scrollBy 完成滑动效果。
属性动画: 动画对 View 进行滑动技术博客大总结

ViewDragHelper: 谷歌提供的辅助类,用于完成各种拖拽效果。

Layout 实现滑动

offsetLeftAndRight 和 offsetTopAndBottom 实现滑动

LayoutParams 实现滑动

通过父控件设置 View 在父控件的位置,但需要指定父布局的类型,不好

用 ViewGroup 的 MariginLayoutParams 的方法去设置 margin
// 方法一:通过布局设置在父控件的位置。但是必须要有父控件, 而且要指定父布局的类型,不好的方法。
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
/**===============================================
* 方法二:用 ViewGroup 的 MarginLayoutParams 的方法去设置 marign
* 优点:相比于上面方法, 就不需要知道父布局的类型。

*===============================================*/
ViewGroup.MarginLayoutParams mlayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
mlayoutParams.leftMargin = getLeft() + offsetX;
mlayoutParams.topMargin = getTop() + offsetY;
setLayoutParams(mlayoutParams);
“`

scrollToscrollBy 实现滑动

都是 View 提供的方法。
scrollTo- 直接到新的 x,y 坐标处。
scrollBy- 基于当前位置的相对滑动。
scrollBy- 内部是调用 scrollTo.
scrollToscrollBy, 效果是移动 View 的内容,因此需要在 View 的父控件中调用。

scrollTo/By 内部的 mScrollX 和 mScrollY 的意义

mScrollX 的值,相当于手机屏幕相对于 View 左边缘向右移动的距离,手机屏幕向右移动时,mScrollX 的值为正;手机屏幕向左移动(等价于 View 向右移动),mScrollX 的值为负。
mScrollY 和 X 的情况相似,手机屏幕向下移动,mScrollY 为 + 正值;手机屏幕向上移动,mScrollY 为 - 负值。
mScrollX/ Y 是根据第一次滑动前的位置来获得的,例如:第一次向左滑动 200(等于手机屏幕向右滑动 200),mScrollX = 200;第二次向右滑动 50, mScrollX = 200 +(-50)= 150,而不是(-50)。

动画实现滑动的方法

可以通过传统动画或者属性动画的方式实现
传统动画需要通过设置 fillAfter 为 true 来保留动画后的状态(但是无法在动画后的位置进行点击操作,这方面还是属性动画好)
属性动画会保留动画后的状态,能够点击。技术博客大总结

ViewDragHelper
通过 ViewDragHelper 去自定义 ViewGroup 让其子 View 具有滑动效果。不过用的很少

8.0.0.7 事件的传递规则是什么?View 处理事件的优先级?点击事件传递过程遵循如下顺序?事件传递规则要点?

事件的传递规则是什么?

点击事件产生后,会先传递给根 ViewGroup,并调用 dispatchTouchEvent
之后会通过 onInterceptTouchEvent 判断是否拦截该事件,如果 true,则表示拦截并交给该 ViewGroup 的 onTouchEvent 方法进行处理
如果不拦截,则当前事件会传递给子元素,调用子元素的 dispatchTouchEvent,如此反复直到事件被处理

View 处理事件的优先级?

在 View 需要处理事件时,会先调用 OnTouchListener 的 onTouch 方法,并判断 onTouch 的返回值
返回 true,表示处理完成,不会调用 onTouchEvent 方法
返回 false,表示未完成,调用 onTouchEvent 方法进行处理
可见,onTouchEvent 的优先级没有 OnTouchListener 高
onTouchEvent 没有消耗的话就会交给 TouchDelegate 的 onTouchEvent 去处理。
如果最后事件都没有消耗,会在 onTouchEvent 中执行 performClick()方法,内部会执行 OnClickListener 的 onClick 方法,优先级最低,属于事件传递尾端

点击事件传递过程遵循如下顺序?技术博客大总结

Activity->Window->View-> 分发
如果 View 的 onTouchEvent 返回 false,则父容器的 onTouchEvent 会被调用,最终可以传递到 Activity 的 onTouchEvent

事件传递规则要点?

View 一旦拦截事件,则整个事件序列都由它处理(ACTION_DOWNUP 等),onInterceptTouchEvent 不会再调用(因为默认都拦截了)
但是一个事件序列也可以通过特殊方法交给其他 View 处理(onTouchEvent)
如果 View 开始处理事件(已经拦截),如果不消耗 ACTIO_DOWN 事件(onTouchEvent 返回 false),则同一事件序列的剩余内容都直接交给父 onTouchEvent 处理
View 消耗了 ACTION_DOWN,但不处理其他的事件,整个事件序列会消失 (父 onTouchEvent) 不会调用。这些消失的点击事件最终会传给 Activity 处理。
ViewGroup 默认不拦截任何事件(onInterceptTouchEvent 默认返回 false)
View 没有 onInterceptTouchEvent 方法,一旦有事件传递给 View,onTouchEvent 就会被调用
View 的 onTouchEvent 默认都会消耗事件 return true, 除非该 View 不可点击(clickable 和 longClickable 同时为 false)
View 的 enable 属性不影响 onTouchEvent 的默认返回值。即使是 disable 状态。
onClick 的发生前提是当前 View 可点击,并且收到了 down 和 up 事件
事件传递过程是由父到子,层层分发,可以通过 requestDisallowInterceptTouchEvent 让子元素干预父元素的事件分发(ACTION_DOWN 除外)

8.0.0.8 Scroller 的作用?Scroller 的要点有哪些?Scroller 的使用步骤?Scroller 工作原理?

Scroller 的作用?

用于封装滑动
提供了基于时间的滑动偏移值,但是实际滑动需要我们去负责。

Scroller 的要点有哪些?

调用 startScroll 方法时,Scroller 只是单纯的保存参数
之后的 invalidate 方法导致的 View 重绘
View 重绘之后 draw 方法会调用自己实现的 computeScroll(),才真正实现了滑动

Scroller 的使用步骤?

Scroller 工作原理?技术博客大总结

Scroller 本身不能实现 View 的滑动,需要配合 View 的 computeScroll 方法实现弹性滑动
不断让 View 重绘,每一次重绘距离滑动的开始时间有一个时间间隔,通过该时间可以得到 View 当前的滑动距离
View 的每次重绘都会导致 View 的小幅滑动,多次小幅滑动就组成了弹性滑动

8.0.0.9 Activity 事件分发的过程?Window 事件分发?DecorView 的事件分发?根 View 的事件分发?

Activity 事件分发的过程?

事件分发过程:Activity->Window->Decor View(当前界面的底层容器,setContentView 的 View 的父容器)->ViewGroup->View
Activity 的 dispatchTouchEvent,会交给 Window 处理(getWindow().superDispatchTouchEvent()),
返回 true:事件全部结束
返回 false:所有 View 都没有处理(onTouchEvent 返回 false),则调用 Activity 的 onTouchEvent

Window 事件分发?

Window 和 superDispatchTouchEvent 分别是抽象类和抽象方法
Window 的实现类是 PhoneWindow
PhoneWindow 的 superDispatchTouchEvent()直接调用 mDecor.superDispatchTouchEvent(), 也就是直接传给了 DecorView

DecorView 的事件分发?

DecorView 继承自 FrameLayout
DecorView 的 superDispatchTouchEvent()会调用 super.dispatchTouchEvent()——也就是 ViewGroup 的 dispatchTouchEvent 方法,之后就会层层分发下去。

根 View 的事件分发?技术博客大总结

顶层 View 调用 dispatchTouchEvent
调用 onInterceptTouchEvent 方法
返回 true,事件由当前 View 处理。如果有 onTouchiListener,会执行 onTouch,并且屏蔽掉 onTouchEvent。没有则执行 onTouchEvent。如果设置了 onClickListener,会在 onTouchEvent 后执行 onClickListener
返回 false,不拦截,交给子 View 重复如上步骤。

8.0.1.0 GestureDetector 是干什么用的?GestureDetector 作用和注意点?有哪些常用的监听方法?

GestureDetector 作用和注意点?

探测手势和事件,需要通过提供的 MotionEvent
该类仅能用于 touch 触摸提供的 MotionEvent,不能用于 traceball events(追踪球事件)
可以在自定义 View 中重写 onTouchEvent()方法并在里面用 GestureDetector 接管。
可以在 View 的 setOnTouchListener 的 onTouch 中将点击事件交给 GestureDetector 接管。

有哪些常用的监听方法?

OnGestureListener
OnDoubleTapListener
OnContextClickListener
SimpleOnGestureListener

OnGestureListener

OnGestureListener 作用技术博客大总结

用于在手势产生时,去通知监听者。
该监听器会监听所有的手势,如果只需要监听一部分可以使用 SimpleOnGestureListener

OnGestureListener 能监听哪些手势

按下操作。
按下之后,Move 和 Up 之前。用于提供视觉反馈告诉用户已经捕获了他们的行为。
抬起操作。
滑动操作(由 Down MotionEvent e1 触发,当前是 Move MotionEvent e2)
长按操作。
猛扔操作。
所有有返回值的回调方法,return true- 消耗该事件;return false- 不消耗该事件

OnDoubleTapListener

OnDoubleTapListener 作用

监听“双击操作”
监听“确认的单击操作”—该单击操作之后的操作无法构成一次双击。

OnDoubleTapListener 能监听哪些手势?

单击操作。
双击操作.
双击操作之间发生了 down、move 或者 up 事件。

8.0.1.2 View 的滑动方式?如何让控件滚动到某一位置?scrollTo()和 scrollBy()的区别?Scroller 是什么?

View 的滑动方式?

三种方式:

a. 通过 View 本身提供的 scrollTo/scrollBy 方法
移动的是 View 的内容,View 本身不移动

b. 通过动画给 View 施加平移效果实现滑动
通过补间动画移动的 View 的影像,View 本身位置不发生改变。通过属性动画移动 view 的影像,view 本身位置会发生改变。

c. 通过改变 View 的 LayoutParams 使 View 重新布局实现滑动
改变布局参数,代码如下:
MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();
params.width += 10;
params.height += 10;
mButton.setLayoutParams(params);

三种方法的使用对比

scrollTo/scrollBy:操作简单,适合对 View 内容的滑动;
动画:操作简单,主要适合于没有交互的 View 和实现复杂的动画效果;
改变布局参数:操作稍微复杂,适用于有交互的 View。

scrollTo()和 scrollBy()技术博客大总结

scrollBy 内部调用了 scrollTo,它是基于当前位置的相对滑动;而 scrollTo 是绝对滑动,因此如果利用相同输入参数多次调用 scrollTo()方法,由于 View 初始位置是不变只会出现一次 View 滚动的效果而不是多次。
引申:两者都只能对 view 内容进行滑动,而不能使 view 本身滑动,且非平滑,可使用 Scroller 有过渡滑动的效果。

Scroller 实现滑动的具体过程:

在 MotionEvent.ACTION_UP 事件触发时调用 startScroll()方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量
马上调用 invalidate/postInvalidate()方法,请求 View 重绘,导致 View.draw 方法被执行
紧接着会调用 View.computeScroll()方法,此方法是空实现,需要自己处理逻辑。具体逻辑是:先判断 computeScrollOffset(),若为 true(表示滚动未结束),则执行 scrollTo()方法,它会再次调用 postInvalidate(),如此反复执行,直到返回值为 false。

8.0.1.4 谈一谈 View 的工作原理,执行流程,MeasureSpec 是什么?有什么作用?

View 工作流程

View 工作流程简单来说就是,先 measure 测量,用于确定 View 的测量宽高,再 layout 布局,用于确定 View 的最终宽高和四个顶点的位置,最后 draw 绘制,用于将 View 绘制到屏幕上。

ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带。
View 的绘制流程是从 ViewRoot 和 performTraversals 开始。
performTraversals()依次调用 performMeasure()、performLayout()和 performDraw()三个方法,分别完成顶级 View 的绘制。
其中,performMeasure()会调用 measure(),measure()中又调用 onMeasure(),实现对其所有子元素的 measure 过程,这样就完成了一次 measure 过程;接着子元素会重复父容器的 measure 过程,如此反复至完成整个 View 树的遍历。layout 和 draw 同理。

MeasureSpec 作用技术博客大总结

通过宽测量值 widthMeasureSpec 和高测量值 heightMeasureSpec 决定 View 的大小

MeasureSpec 组成:一个 32 位 int 值,高 2 位代表 SpecMode(测量模式),低 30 位代表 SpecSize
直接继承 View 的自定义 View 需要重写 onMeasure()并设置 wrap_content 时的自身大小,否则效果相当于 macth_parent

SpecMode 有三类:

UNSPECIFIED 表示父容器不对 View 有任何限制,一般用于系统内部,表示一种测量状态;
EXACTLY 父容器已经检测出 view 所需的精确大小,这时候 view 的最终大小 SpecSize 所指定的值,相当于 match_parent 或指定具体数值。
AT_MOST 父容器指定一个可用大小即 SpecSize,view 的大小不能大于这个值,具体多大要看 view 的具体实现,相当于 wrap_content。

8.0.1.6 SurfaceView 和 View 的区别,说一下 SurfaceView 的工作原理,为何不会导致页面卡顿?

SurfaceView 是从 View 基类中派生出来的显示类,他和 View 的区别有:

View 需要在 UI 线程对画面进行刷新,而 SurfaceView 可在子线程进行页面的刷新
View 适用于主动更新的情况,而 SurfaceView 适用于被动更新,如频繁刷新,这是因为如果使用 View 频繁刷新会阻塞主线程,导致界面卡顿技术博客大总结

SurfaceView 在底层已实现双缓冲机制,而 View 没有,因此 SurfaceView 更适用于需要频繁刷新、刷新时数据处理量很大的页面

关于其他内容介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

正文完
 0