抛出一个问题:View是在什么时候添加到屏幕上的呢?
是setContentView吗? 不算是,因为setContentView仅仅是创建了DecrView,并将Xml中的View添加到DecrView中,而DecrView的内容并未真正显示在屏幕上,真正的显示是在绘制完成后
相信有同学会经常遇到,view.getMeasuredHeight() == 0的,这种情况一般发生在:
在activity中的onCreate或者onResume去获取view的高度/宽度时候发生,因为这个时候View的绘制并未完成,因此拿到的值是0,如果非要在onCreate中获取到宽高呢,一般采用view.post方法,交给handler异步获取
通过阅读源码,View真正绘制流程的入口在:ViewRootImpl.setView()中执行的 该方法实在AcivityOnReume方法之后去调用的,在该方法中调用了requestLayout()方法
调用链如下: ViewRootImpl.setView() --> requestLayout() --> scheduleTraversals() --> mChoreographer.postCallback(mTraversalRunnable) --> doTraversal() --> performTraversals()
在performTraversals()中就开始了View的绘制流程的工作,onDraw、onMeasure、onLayout等方法
另外在ViewRootImpl.setView()中还有一段
view.assignParent(this);//这个表示所有的View的最终父类都是ViewRootImpl对象
在多提一嘴,在ViewRootImpl的构造方法中,有几个需要认识属性
mDirty脏区域,用于当界面部分UI发生改变时只需要刷新脏区即可
mThread = Thread.currentThread(); //获取创建当前View 的线程,刷新UI时会进行线程检查
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);// 通过mAttachInfo获取窗口信息
接下来重点阐述以下performTraversals() 方法所作的事情:
1、预测量
在该方法中调用了三次performMeasure方法,主要是针对WrapContent情况下进行的预测量
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
2、布局窗口(跟WMS有关,后面在研究)
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
3、开始测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
--> View.measure()
--> View.onMeasure()
重写了View 的onMeasure()方法一定要setMeasuredDimension()以下,否则会报错
MeasureSpec 是一个32位的int值,低30位是值,高两位是模式(一共三种模式)
4、开始布局
performLayout(lp, mWidth, mHeight);
--->View.layout
--->setFrame(l, t, r, b)//设置左上右下的值
--->onLayout()
--->onLayoutChange()//监听布局变化
注意:如果是View,需要加上自己的Padding属性值 如果是容器,需要加上孩子的Margin值
5、开始绘制
performDraw();
-->draw()
-->onDraw()
--->dispatchDraw()
注意想要让自定义viewGroup执行Ondraw,需要在构造方法中设置setWillNotDraw(false)或者直接使用dispatchDraw()也可以
面试题: 1、UI刷新只能在主线程吗,实际上并不是,之所以在子线程刷新UI时,会报错的原因是因为ViewRootImpl的requestLayout()中会调用checkThread()方法
准确来讲,应该是在哪个线程创建的View,就应该在哪个线程去刷新UI,而默认创建View的线程都是主线程