阅读源码之-------View的绘制流程

163 阅读2分钟

抛出一个问题: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()以下,否则会报错

image.png

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()方法

image.png

image.png 准确来讲,应该是在哪个线程创建的View,就应该在哪个线程去刷新UI,而默认创建View的线程都是主线程

View是如何被渲染的

image.png