目录
Q1:MotionEvent是什么?包含几种事件?什么条件下会产生?
Q3:Scroller中最重要的两个方法是什么?主要目的是?
Q8:onTouch()、onTouchEvent()和onClick()关系?
Q10:invalidate()和postInvalidate()的区别?
Q1:MotionEvent是什么?包含几种事件?什么条件下会产生?
MotionEvent:是手指触摸屏幕锁产生的一系列事件。典型事件有:
- ACTION_DOWN:手指刚接触屏幕
- ACTION_MOVE:手指在屏幕上滑动
- ACTION_UP:手指在屏幕上松开的一瞬间
事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件
任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件。如图:
Q2:scrollTo()和scrollBy()的区别?
Q3:Scroller中最重要的两个方法是什么?主要目的是?
跟Q2相同。
Q4:谈一谈View的事件分发机制?
a.事件分发本质:就是对MotionEvent事件分发的过程。即当一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。(关于MotionEvent介绍见本篇2.a)
b.点击事件的传递顺序:Activity(Window) -> ViewGroup -> View
c.需要的三个主要方法:
-
dispatchTouchEvent:进行事件的分发(传递)。返回值是 boolean 类型,受当前onTouchEvent和下级view的dispatchTouchEvent影响
-
onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,所以后面的事件都会交给ViewGroup处理。
-
onTouchEvent:进行事件处理。
事件分发过程图:
- 事件分发是逐级下发的,目的是将事件传递给一个View。
- ViewGroup一旦拦截事件,就不往下分发,同时调用onTouchEvent处理事件。
Q5:如何解决View的滑动冲突?
a.产生原因:
- 一般情况下,在一个界面里存在内外两层可同时滑动的情况时,会出现滑动冲突现象。
b.可能场景:
- 外部滑动和内部滑动方向不一致:如ViewPager嵌套ListView(实际这么用没问题,因为ViewPager内部已处理过)。
- 外部滑动方向和内部滑动方向一致:如ScrollView嵌套ListView(实际上也已被解决)。
- 上面两种情况的嵌套
c.处理规则:
- 对场景一:当用户左右/上下滑动时让外部View拦截点击事件,当用户上下/左右滑动时让内部View拦截点击事件。即根据滑动的方向判断谁来拦截事件。关于判断是上下滑动还是左右滑动,可根据滑动的距离或者滑动的角度去判断。
- 对场景二:一般从业务上找突破点。即根据业务需求,规定何时让外部View拦截事件何时由内部View拦截事件。
- 对场景三:相对复杂,可同样根据需求在业务上找到突破点。
d.解决方式:
- 法一:外部拦截法
- 含义:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。
- 方法:需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截。以下是代码:
//重写父容器的拦截方法
public boolean onInterceptTouchEvent (MotionEvent event){
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://对于ACTION_DOWN事件必须返回false,一旦拦截后续事件将不能传递给子View
intercepted = false;
break;
case MotionEvent.ACTION_MOVE://对于ACTION_MOVE事件根据需要决定是否拦截
if (父容器需要当前事件) {
intercepted = true;
} else {
intercepted = flase;
}
break;
}
case MotionEvent.ACTION_UP://对于ACTION_UP事件必须返回false,一旦拦截子View的onClick事件将不会触发
intercepted = false;
break;
default : break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
法二:内部拦截法:
- 含义:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
- 方法:需要配合requestDisallowInterceptTouchEvent方法。以下是子View的dispatchTouchEvent方法的代码:
public boolean dispatchTouchEvent ( MotionEvent event ) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);//为true表示禁止父容器拦截
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default :
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
除子容器需要做处理外,父容器也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子容器调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。因此,父View需要重写onInterceptTouchEvent方法:
public boolean onInterceptTouchEvent (MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
内部拦截法要求父容器不能拦截ACTION_DOWN的原因:由于该事件并不受FLAG_DISALLOW_INTERCEPT(由requestDisallowInterceptTouchEvent方法设置)标记位控制,一旦ACTION_DOWN事件到来,该标记位会被重置。所以一旦父容器拦截了该事件,那么所有的事件都不会传递给子View,内部拦截法也就失效了。
Q6:谈一谈View的工作原理?
1.View工作流程:measure测量->layout布局->draw绘制
- measure确定View的测量宽高
- layout确定View的最终宽高和四个顶点的位置
- draw将View 绘制到屏幕上
- 对应onMeasure()、onLayout()、onDraw()三个方法。
具体过程:
- ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带。
- View的绘制流程是从ViewRoot和performTraversals开始。
- performTraversals()依次调用performMeasure()、performLayout()和performDraw()三个方法,分别完成顶级 View的绘制。
- 其中,performMeasure()会调用measure(),measure()中又调用onMeasure(),实现对其所有子元素的measure过程,这样就完成了一次measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。过程图如下:
Q7:MeasureSpec是什么?有什么作用?
View的MeasureSpec确定过程(onMeasure分析) (失效)
自定义View系列教程02--onMeasure源码详尽分析
SpecMode一共有三种:
MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , MeasureSpec.UNSPECIFIED
MeasureSpec.EXACTLY
官方文档的描述:
The child can be as large as it wants up to the specified size.
MeasureSpec.EXACTLY模式表示:父容器已经检测出子View所需要的精确大小。
在该模式下,View的测量大小即为SpecSize。
MeasureSpec.AT_MOST
官方文档的描述:
The child can be as large as it wants up to the specified size.
MeasureSpec.AT_MOST模式表示:父容器未能检测出子View所需要的精确大小,但是指定了一个可用大小即specSize
在该模式下,View的测量大小不能超过SpecSize。
MeasureSpec.UNSPECIFIED
官方文档的描述:
The parent has not imposed any constraint on the child. It can be whatever size it wants.
父容器不对子View的大小做限制.
MeasureSpec.UNSPECIFIED这种模式一般用作Android系统内部,或者ListView和ScrollView等滑动控件,在此不做讨论。
Q8:onTouch()、onTouchEvent()和onClick()关系?
onTouch和onTouchEvent以及onClick的顺序
Q9:SurfaceView和View的区别?
Q10:invalidate()和postInvalidate()的区别?
android中Invalidate和postInvalidate的区别