参考:
- 2019校招Android面试题解1.0(上篇)
- 通过该博客去抓问题的重点,问题整合一篇之后,再深入,做更全面的梳理;
- 《Android开发艺术探索》
- 《Android群英转》
一.MotionEvent
-
1.是什么?
-
2.包含几种事件?
-
3.什么条件下会产生?
ACTION_DOWN:手指刚接触屏幕
- ACTION_MOVE:手指在屏幕上滑动
- ACTION_UP:手指在屏幕上松开的一瞬间
- ACTION_CANCEL:手指保持按下操作,并从当前控件转移到外层控件时会触发
二.scrollTo()和scrollBy()的区别?
- 技术点:View滑动
- 参考回答:
- scrollBy内部调用了scrollTo,它实现了基于当前位置的相对滑动;而scrollTo则实现了基于所传递参数的绝对滑动;
- scrollBy和scrollTo来实现View的滑动,只能将View的内容进行移动,也就是说,不管怎么滑动,也不能将View本身进行移动;
- 补充
- 实现View滑动有三种方式
- 通过View本身提供的scrollBy和scrollTo;
- 通过动画给View施加平移效果来实现滑动;
- 通过改变View的LayoutParams使得View重新布局从而实现滑动;
弹性滑动(将大的滑动分成若干个小的滑动并在一个时间段内完成)
技术点:View滑动
- Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。
View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动;
四.Scroller中最重要的两个方法是什么?主要目的是?
技术点:View滑动
- 最重要的两个方法是startScroll()和computeScroll()
- Scroller实现滑动的流程图
五.View的事件分发机制
- 思路:从分发本质、传递顺序、核心方法展开(思路不错)
- 事件分发本质:就是对MotionEvent事件分发的过程。即当一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上;
- 点击事件的传递顺序:Activity(Window) -> ViewGroup -> View;
- 三个主要方法:
- dispatchTouchEvent:进行事件的分发(传递)。返回值是 boolean 类型,受当前onTouchEvent和下级view的dispatchTouchEvent影响
- onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,所以后面的事件都会交给ViewGroup处理。
- onTouchEvent:进行事件处理。
补充:单击事件由Window传递给DecorView,然后再由DecorView传递给我们的View;
六.如何解决View的滑动冲突?
技术点:View滑动冲突
- 处理规则:
- 方向不一致,根据方向判断谁来处理事件;
- 方向一致:根据业务需求,何时让外部View拦截事件,何时由内部View拦截事件;
- 嵌套:根据需求在业务上赵突破点;
实现方式
技术点: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同理。
补充:
- ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的;
- 在ActivityThread当中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl和DecorView关联;
- View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来;
- performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中在performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw的传递流程和performMeasure是类似的;
八.MeasureSpec是什么?有什么作用?
技术点:View工作流程(measure)
- 思路:从MeasureSpec作用、组成、模式和决定因素展开
- MeasureSpec:代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize( 某种测量模式下的规格大小)。
- MeasureSpec在很大程度上决定了一个View的尺寸规格,父容器影响View的MeasureSpec的创建过程。
- 在测量过程中,系统会将LayoutParams根据父容器施加的规则转换成MeasureSpec,然后在根据这个MeasureSpec测量出View的宽/高;
- 三种模式
- UNSPECIFIED:父容器不对View有任何限制,要多大有多大。常用于系统内部。
- EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
- AT_MOST(最大模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。
- 普通View的MeasureSpec创建规则
九.自定义View须知
- 1.让View支持wrap_content
- 因为直接继承View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content时就无法达到预期的效果;
- 2.如果有必要,让你的View支持padding
- 因为直接继承View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用的。另外,直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。
- 3.尽量不要在View中使用HandIer,没必要
- 因为View内部本身就提供了post系列的方法,完全可以替代Handler的作用,当然除非你很明确地要使用Handler来发送消息。
- 4.View中如果有线程或者动画,需要及时停止
- 在onDetachedFromWindow方法中处理,防止内存泄漏。
- 当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow,当包含此View的Activity启动时,View的onAttachedToWindow方法会被调用。
-
十.onTouch()、onTouchEvent()和onClick()关系?
技术点:View事件分发
- 优先度onTouch()>onTouchEvent()>onClick()。因此onTouchListener的onTouch()方法会先触发;如果onTouch()返回false才会接着触发onTouchEvent(),同样的,内置诸如onClick()事件的实现等等都基于onTouchEvent();如果onTouch()返回true,这些事件将不会被触发;
onClick()
会通过performClick()
完成点击事件的;十一.SurfaceView和View的区别?
View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新;
- View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新;
- View在绘制时没有使用双缓冲机制,而SurfaceView在底层实现中就已经实现了双缓冲机制;
总结:如果你的自定义View需要频繁刷新,或者刷新时处理数据量比较大,那么就可以考虑使用SurfaceView替代View;
十二.invalidate()和postInvalidate()的区别?
技术点:View刷新
- invalidate()与postInvalidate()都用于刷新View;
- 主要区别是invalidate()在主线程中调用,若在子线程中使用需要配合handler;而postInvalidate()可在子线程中直接调用。