View的绘制流程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了View的绘制流程相关的知识,希望对你有一定的参考价值。

参考技术A 一个应用启动时,会启动一个主Activity,android 系统会根据activity的布局来对它进行绘制。绘制会从根视图ViewRoot的performTraversals()

方法开始,从上到下遍历整个视图树,每个view控制负责绘制自己,而ViewGroup还需要负责通知自己的子view进行绘制操作。视图绘制的过程

可以分为三个步骤,分别是测量(measure)、布局(layout)和绘制(Draw)。

performTraversals()方法中主要分为三个步骤:performMeasure、performLayout、performDraw

Measure:performMeasure()方法中执行view的measure方法,具体的测量操作是分发给ViewGroup的,由ViewGroup在它的measureChild方法中传递

给子view,遍历所有的子view,逐个调用子view的measure方法。最终测量是通过回调onMeasure方法实现的。如果要自定义测量过程。则子类

可以重写onMeasure方法,如果没有重写onMeasure方法,则默认会直接调用getDefaultSize来获得View的宽高。

Layout:layout过程用来确定View在父容器中的布局位置,它是由父容器获取子view的位置参数后,调用view的layout方法并将位置参数传入实现的,

performLayout  --->layout----->onLayout(changed,l,t,r,b)

子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有view控件的布局流程。

Draw:draw操作用来将控件绘制出来,绘制的流程从performDraw方法开始,

performDraw----> draw----->

绘制每个具体的view,绘制基本上可以分为六个步骤:

(1)drawBackground(canvas)绘制view的背景

(2)canvas.getSaveCount()  canvas.saveLayer( )  需要的话,保存canvas的图层,为fading做准备

(3)onDraw(canvas) 绘制view的内容

(4)dispatchDraw(canvas)绘制view的子view

(5)canvas.drawRect()  canvas.restoreToCount()如果需要的话,绘制view的fading边缘并恢复图层

(6)绘制view的装饰(例如滚动条)

view的绘制几个方法需要追溯到android本身的页面结构,首先整体上市一个phoneWindow对象,然后是一个DecorView,DecorView本身是一个Framelayout,内部维护一个ViewStub的toolBar,然后下面是一个FrameLayout,,也就是我们通常setContentView中的content内容,然后是绘制,首先会触发DecorView的onMeasure()方法,它的测量规则包含了手机屏幕的宽高,并且测量模式是MeasureSpec.EXACTLY,

几种父布局测量模式与子view测量模式组合:

1、当viewGroup是MeasureSpec.EXACTLY:
(1)子view是具体值,例如200dp,那么子view的显示是这个具体值,mode是EXACTLY;
(2)子view是MATCH_PARENT,那么子view的显示是viewGroup的size,mode是EXACTLY;
(3)子view是wrap_content,那么子view显示viewGroup的size,mode是AT_MOST。

2、当viewGroup是MeasureSpec.AT_MOST:

(1)子view是具体值,例如200dp,那么子view显示这个具体值,mode是EXACTLY;
(2)子view是MATCH_PARENT,那么子view显示是viewGroup的size,mode是AT_MOST;
(3)子view是wrap_content,那么子view显示是viewGroup的size,mode是AT_MOST。

3、当ViewGroup是MeasureSpec.UNSPECIFIED:
(1)子view是具体值,例如200dp,那么子view显示这个具体值,mode为EXACTLY;
(2)子view是MATCH_PARENT,子view显示是viewGroup的size,mode是UNSPECIFIED;
(3)子view是wrap_content,那么子view显示viewGroup的size,mode是UNSPECIFIED。

View绘制流程(一)

参考技术A

最近在学习 View 的绘制流程,看了几篇不错的博客( ViewRootImpl的独白,我不是一个View(布局篇) 、 Android应用层View绘制流程与源码分析 )自己对照源码,梳理了一遍。

在Activity的onResume之后,当前Activity的Window对象中的View会被添加在WindowManager中。

整个View树的绘图流程是在 ViewRootImpl 类的 performTraversals() 方法(这个方法巨长)开始的,该方法做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小 (measure) 、是否重新放置视图的位置 (layout) 、以及是否重绘 (draw) ,其核心也就是通过判断来选择顺序执行这三个方法。

ViewRootImpl 调用 performMeasure 执行Window对应的View的测量。

int widthMeasureSpec :他由两部分组成, 高2位表示MODE ,定义在MeasureSpec类(View的内部类)中,有三种类型, MeasureSpec.EXACTLY 表示确定大小, MeasureSpec.AT_MOST 表示最大大小, MeasureSpec.UNSPECIFIED 不确定。 低30位表示size ,也就是父View的大小。对于系统Window类的DecorVIew对象Mode一般都为MeasureSpec.EXACTLY ,而size分别对应屏幕宽高。对于子View来说大小是由父View和子View共同决定的。

默认的尺寸大小即传入的参数都是通过 getDefaultSize 返回的,我们就看一下该方法的实现。

到此一次最基础的元素View的 measure 过程就完成了。

View实际是嵌套的,而且measure是递归传递的,所以每个View都需要 measure ,能够嵌套的View都是ViewGroup的子类,所以在ViewGroup中定义了 measureChildren , measureChild , measureChildWithMargins 方法来对子视图进行测量, measureChildren 内部实质只是循环调用 measureChild , measureChild 和 measureChildWithMargins 的区别就是是否把 margin padding 也作为子视图的大小。 ViewGroup 本身不调用 measureChildWithMargins 和 measureChildren 方法,由继承类通过for循环调用此方法进行子View的测量。下面看一下ViewGroup中稍微复杂的 measureChildWithMargins 方法。

getChildMeasureSpec 的逻辑是通过其父View提供的 MeasureSpec 参数得到 specMode 和 specSize ,然后根据计算出来的 specMode 以及子View的 childDimension (layout_width或layout_height)来计算自身的 measureSpec ,如果其本身包含子视图,则计算出来的 measureSpec 将作为调用其子视图 measure 函数的参数,同时也作为自身调用 setMeasuredDimension 的参数,如果其不包含子视图则默认情况下最终会调用 onMeasure 的默认实现,并最终调用到 setMeasuredDimension 。

Activity 的 onResume 之后,当前 Activity Window 对象中的View(DecorView)会被添加在 WindowManager 中。也就是在 ActivityThread 的 handleResumeActivity 方法中调用 wm.addView(decor, l); 将DecorView添加到 WindowManager 中;

WindowManager 继承 ViewManager ,它的实现类为 WindowManagerImpl ,该类中的方法的具体实现是由其代理类 WindowManagerGlobal 实现的;

在它的 addView 方法中会创建 ViewRootImpl 的实例,然后将Window对应的View(DecorView),ViewRootImpl,LayoutParams顺序添加在WindowManager中,最后将Window所对应的View设置给创建的ViewRootImpl,通过 ViewRootImpl 来更新界面并完成Window的添加过程;

设置view调用的是 ViewRootImpl 的 setView 方法,在该方法中调用 requestLayout(); 方法来异步执行view的绘制方法;之后将 Window 添加到屏幕,通过 WMS (跨进程通信)

在 requestLayout 方法中最终会调用 ViewRootImpl 的 performTraversals(); 方法,该方法做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小 (measure) 、是否重新放置视图的位置 (layout) 、以及是否重绘 (draw) ,其核心也就是通过判断来选择顺序执行这三个方法: performMeasure 、 performLayout 、 performDraw ;

在 performMeasure 方法中调用的是 View 的 measure 方法,该方法是 final 修饰,不能被子类重写,在该方法中实际调用的是 View 的 onMeasure 方法,子类可以重写 onMeasure 方法来实现自己的测量规则。

View 默认的 onMeasure 方法很简单只是调用了 setMeasuredDimension 方法,该方法的作用是给 View 的成员变量 mMeasuredWidth mMeasuredHeight 赋值,View的测量主要就是给这两个变量赋值,这两个变量一旦赋值,也就意味着测量过程的结束。

setMeasuredDimension 方法传入的尺寸是通过 getDefaultSize(int size, int measureSpec); 方法返回的,在
getDefaultSize 方法中解析 measureSpec Mode Size ,如果Mode为 MeasureSpec.AT_MOST 或者 MeasureSpec.EXACTLY ,最终的size的值为解析后的size;如果 Mode MeasureSpec.UNSPECIFIED ,最终的size为建议的最小值= getSuggestedMinimumWidth ,该方法的具体实现为 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); ,建议的最小宽度和高度都是由View的Background尺寸与通过设置View的 miniXXX 属性共同决定的

measureSpec 是由 getRootMeasureSpec 方法决定的: measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY); 根布局的大小是 Window 的大小,Window大小是不能改变的,总是全屏的。

View实际是嵌套的,而且measure是递归传递的,所以每个View都需要measure,能够嵌套的View都是ViewGroup的子类,所以在ViewGroup中定义了 measureChildren , measureChild , measureChildWithMargins 方法来对子视图进行测量, measureChildren 内部实质只是循环调用 measureChild , measureChild 和 measureChildWithMargins 的区别就是是否把 margin padding 也作为子视图的大小。

measureChildWithMargins 方法的作用就是对 父View 提供的 measureSpec 参数结合 子View LayoutParams 参数进行了调整,然后再来调用 child.measure() 方法,具体通过方法 getChildMeasureSpec 方法来进行参数调整。计算出来自身的 measureSpec 作为调用其子视图 measure 方法的参数,同时也作为自身调用 setMeasuredDimension 的参数,如果其不包含子视图则默认情况下最终会调用 onMeasure 的默认实现,并最终调用到 setMeasuredDimension 。

最终决定 View measure 大小是 View 的 setMeasuredDimension 方法,该方法就是设置mMeasuredWidth和mMeasuredHeight的大小,ViewGroup在 onMeasure 方法调用 setMeasuredDimension 之前调整了 measureSpec

以上是关于View的绘制流程的主要内容,如果未能解决你的问题,请参考以下文章

View的绘制流程

View的绘制流程源码分析

Android面试View的绘制流程

公共技术点之 View 绘制流程

Android View 绘制流程(Draw) 完全解析

Android View 绘制流程