评论

收藏

[Android] AndroidUI绘制流程

移动开发 移动开发 发布于:2022-07-19 18:16 | 阅读数:259 | 评论:0

基础知识-底层View对象
ViewParent
ViewParent对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带。View绘制三大流程都是通过它来完成的
DecorView
ContentView的父容器
ViewRootImpl


在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联,将 DecorView 实例对象交给 ViewRootImpl 用以绘制 View 。最后调用 ViewRootImpl 类中的 performTraversals(),从而实现视图的绘制。正文
根据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()//完成宽高成员变量的赋值


一个布局中一般都会包含多个子视图,每个视图都需要经历一次 measure 过程。ViewGroup 中定义了一个 measureChildren() 、measureChild()、measureChildWithMargins() 方法来去测量子视图的大小,三个方法最终都是调用子视图的 measure() 方法

对于非 ViewGroup 的 View 而言,通过调用上面默认的 onMeasure() 即可完成 View 的测量(当然你也可以重载 onMeasure() 并调用 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 树。
未完待续


   
   
   
                        

关注下面的标签,发现更多相似文章