AndroidUI绘制流程
基础知识-底层View对象
ViewParent
ViewParent对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带。View绘制三大流程都是通过它来完成的
DecorView
ContentView的父容器
ViewRootImpl
正文
根据APP启动流程,从ActivityThread.handleLaunchActivity()开始,调用了performLaunchActivity() 和 handleResumeActivity()两个方法。下面分别讲一下。
performLaunchActivity
performLaunchActivity()方法中,创建了Activity对象,执行了生命周期,创建了PhoneWindow对象,初始化DecorView,添加布局到DecorView的Content。
PhoneWindow对象创建:performLaunchActivity()->activity.attach()->mWindow = new PhoneWindow()
初始化DecorView:PhoneWindow.setContentView()->installDecor()->创建decorView对象,为decorView的contentView设置布局
添加布局到DecorView的Content : PhoneWindow.setContentView()->mLayoutInflater.inflate(layoutResID, mContentParent); ?
handleResumeActivity
handleResumeActivity()方法中,调用了Activity.onResume,wm.addview()。
我们看看 WindowManagerGlobal 的 addView() 方法。
handleResumeActivity()->WindowManagerGlobal.addview()->ViewRootImpl.setView()->ViewRootImpl->requestLayout()->ViewRootImpl.scheduleTraversals()->ViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)->ViewRootImpl.performTraversals()
——》performMeasure()计算View树种各个空间显示,需要多大的尺寸
performLayout()从根View开始,递归完成UI的布局工作
performDraw()从根View开始,完成所有所有View和ViewGroup的绘制工作,根据布局过程计算出的显示区域,将内容画到屏幕上。
我们都知道一个视图要经历三个主要流程,onDraw,onMeasure,onLayout。他们明显对应上述的几个方法
performMeasure
performMeasure()->mView.measure()//根据强制布局标志位和needsLayout标识位决定是否测量->View.onMeasure()->View.setMeasuredDimension()//完成宽高成员变量的赋值
performLayout
performLayout()->host.layout()//host即DecorView->ViewGroup.layout()->super.layout()->View.Layout()
->View.onLayout()
setOpticalFrame()
setFrame()
根据之前measure测量的尺寸和其他属性,确定View的位置
layout() 方法会调用 setFrame() 方法,setFrame() 方法是真正执行布局任务的步骤,至于 setOpticalFrame() 方法,其中也是调用 setFrame() 方法,通过设置 View 的 mLeft、mTop、mRight 和 mBottom 四个参数来执行布局,对应描述了 View 相对其父 View 的位置。
在 setFrame() 方法中会判断 View 的位置是否发生了改变,以确定有没有必要对当前的视图进行重绘。
而对子 View 的局部是通过 onLayout() 方法实现的,由于非 ViewGroup 视图不含子 View,所以 View 类的 onLayout() 方法为空,正因为 layout 过程是父布局容器布局子 View 的过程,onLayout() 方法对叶子 View 没有意义,只有 ViewGroup 才有用
再看一下ViewGroup.onlayout()方法
onLayout()->layoutChildren()->child.layout()
可以看得到,这里面也是对每一个子视图调用 layout() 方法的。如果该子视图仍然是父布局,会继续递归下去;如果是叶子 view,则会走到 view 的 onLayout() 空方法,该叶子view布局流程走完。
在 onLayout() 过程结束后,我们就可以调用 getWidth() 方法和 getHeight() 方法来获取视图的宽高值.
这一阶段主要是根据上一阶段得到的测量值来确定View的最终位置
performDraw
performDraw()->draw()->drawSoftware()->DecorView.draw()
有7个步骤,我们重点分析其中两个
onDraw
这个方法里面什么都没做,但是注释讲得很清楚,重写该方法以完成你想要的绘制。因为每个 View 的内容部分是各不相同的,所以需要由子类去实现具体逻辑。以 DecorView 为例,这里 ViewGroup 和 FrameLayout 都没有重写 onDraw() 方法,只有 DecorView 重写了该方法。DecorView 重写 onDraw() 在里面实现自己需要的绘制。
dispatchDraw
注释说明了如果 View 包含子类需要重写该方法,实际上对于叶子 View 来说,该方法没有什么意义,因为它没有子 View 需要画了,而对于 ViewGroup 来说,就需要重写该方法来画它的子 View。
RelativeLayout、LinearLayout、DecorView 之类的布局并没有重写 dispatchDraw() 方法,那我们就直接来看 ViewGroup 里面:
遍历子 View ,调用 drawChild(),以绘制每个子视图:
drawChild() 方法里面直接就只有调用子 View 的 draw() 方法,非常明了。同样的,如果该子 View 还有子视图,也会继续遍历下去调用 drawChild() 方法,继续绘制子 View,直到叶子 View 为止,这样不断递归下去,直到画完整棵 DecorView 树。
未完待续