相信大家在面试的时候经常会遇到自定义view相关的面试题,下面我给大家说一下回答的思路。
1、首先android的绘制流程,在activity创建的时候,ActivityThread会调用handleResumeActivity,此时DecorView会和ViewRootImpl会相关联,并把ViewRootImpl设置为DecorView的父类,View的绘制是从ProformTraverslas开始的。
2、接着就可以分析常见的三个方法 measure layout draw
3、如果有事件相关的要求的重写onTouchEvent方法 同时可以扩展回答事件拦截。
4、可以着扩展viewDragHalper 和gesturepractice 的区别以及用法
5、可以扩展到android 双缓存屏幕刷新机制,以及绘制卡顿等问题。
能回答到上面5点基本自定义view算完事。
下面我来一点点的分析
1、ActivityThread会收到内部类IApplicationThread发送的一个创建Activity的消息(AMS是如何给IApplicationThread发消息的这属于IBinder相关知识了),这个方法名为handleLancherActivity,在这个方法里面主要是顺序的调用Activity的生命周期了。performLancherActivity(用于activity的创建)、handleResumeActivity(用于绘制相关的是接下来主要分析的方法),performPauseActivityIfNeed(activity不可见时调用)
手撕一下performLancherActivity。如下图
2、接下来说onmeasure onlayout ondraw
onmeasure主要主要是测量控件的大小,详细说一下测量模式,在重写onMesurce的时候主要有两个参数widthMeasurceSpace 和heightMeasurceSpace,两个差不多,这里说一下widthMeasurceSpace。
widthMeasurceSapce 是一个32位的int值,前两位表示的是宽高的模式,分别有三种exactly(准确的)acmost(至少)和unspecified(未确认的),后面的30位标识具体的期望值。
widthMeasurceSapce 是由父类传递过来的,具体计算规则
1、如果父控件值确定宽度,子控件也确定宽度,那么MeasurceSapce 的mode 是exactly size是子控件的大小。
2、如果父控件确定宽度,子控件是match_parent,那么
MeasurceSapce的mode 是exactly size是父控件的大小。
3、如果控件确定宽度,子控件是wrap_content,那么MeasurceSapce的mode 是at_most size小于父控件size。
4、父控件是at_most,子控件确定宽度,那么measurceSapce的mode 是exactly size是子控件大小。
5、父控件是at_most,子控件为wrap_content,那么measurceSpace的mode at_most size是包含自己的内容的size
6、父控件是at_most,子控件是match_parent,mode是at_most size是父控件size。
7、父控件是未指定的,子控件是指定的 mode是exactly size是0.
8、父控件是未指定的,子控件是wrap_content 或是match_parent mode是未指定 size是0。
最后调用setMeasureDimension将测量的数据保存下来。
onlayout 主要是ViewGroup调用了根据自己的规则来放自View的位置。
ondraw ondraw方法里面会获取一个canvas,我可以用这个canvas来画我们想画的东西,例如矩形 椭圆 弧线 bitmap text等,也可以位画笔添加颜色风格等。可用通过requestLayout 来重新测量和布局,invalidate来重新绘制。
下来说一下事件的传递机制,在自动控件中也会用到这方面的相关知识,何时让父view响应事件何时子控件响应事件。
主要注意三个方法 dispathTouchEvent、onInterceptTouchEvent,和onTouchEvent,其中view控件是没有onInterceptTouchevent方法的。
ViewGroup 的dispathTouchEvent主要分为三步
1、是否拦截事件。
2、调用dispatchTransformedTouchEvent 递归dispathTouchEvent分发事件。
3、根据mFristTouchTarget,重新分发事件。
是否拦截事件 会根据down事件和mFristTouchTarget!= null来判断是否调用拦截相关的逻辑,所以down事件一定会走onInterceptTouchevent相关方法,或者有子view获取了Touch事件mFristTouchTarget不为null也会走相关拦截逻辑。
遍历ViewGroup中的所有子view,找到触摸点和view一致的view,调用子View的disPathTouchEvent方法,在这里面会先判断TouchListener有没有消耗事件,如果没有则会调用onTouchEvent方法返回true,则会将此View赋值给ViewGroup的mFristTouchTarget对象,并记录事件已经下发。
mFristTouchTarget 对mFristTouchTarget进行判断,如果为空则直接调用viewGroup自己的onToucheEvent方法。如果不为空但是父控件拦截了事件,则会下发cancel事件。(例如ScrollView里面添加一个子view,当我们触摸子View的时候事件能够正常传递到子view,但是当滑动的时候会触发ScrollView控件的拦截事件,scrollview收到事件,上一个子view只能收到一个cancel事件)
mFristTouchTarget 是一个链表,每一个手指的down都可以被记录成一个TouchTarget
requestDisallowInterceptTouchEvent(true) 要求父控件不拦截事件。
viewDragHalper 和gesturepractice 只是对onTouchEvent进行封装,帮助开发者更快更简单的处理拖拽事件(ViewDragHalper)和手势处理事件(gesturepractice)。
屏幕刷新机制可以参考https://juejin.im/post/5d837cd1e51d4561cb5ddf66