一、View绘制的流程框架

View的绘制是从上往下一层层迭代下来的。DecorView-->ViewGroup(--->ViewGroup)-->View ,按照这个流程从上往下,依次measure(测量),layout(布局),draw(绘制)。

二、Measure流程

顾名思义,就是测量每个控件的大小。

调用measure()方法,进行一些逻辑处理,然后调用onMeasure()方法,在其中调用setMeasuredDimension()设定View的宽高信息,完成View的测量操作。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
}

measure()方法中,传入了两个参数 widthMeasureSpec, heightMeasureSpec 表示View的宽高的一些信息。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

由上述流程来看Measure流程很简单,关键点是在于widthMeasureSpec, heightMeasureSpec这两个参数信息怎么获得?

如果有了widthMeasureSpec, heightMeasureSpec,通过一定的处理(可以重写,自定义处理步骤),从中获取View的宽/高,调用setMeasuredDimension()方法,指定View的宽高,完成测量工作。

MeasureSpec的确定

先介绍下什么是MeasureSpec?

MeasureSpec由两部分组成,一部分是测量模式,另一部分是测量的尺寸大小。

其中,Mode模式共分为三类

UNSPECIFIED :不对View进行任何限制,要多大给多大,一般用于系统内部

EXACTLY:对应LayoutParams中的match_parent和具体数值这两种模式。检测到View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值,

AT_MOST :对应LayoutParams中的wrap_content。View的大小不能大于父容器的大小。

那么MeasureSpec又是如何确定的?

对于DecorView,其确定是通过屏幕的大小,和自身的布局参数LayoutParams。

这部分很简单,根据LayoutParams的布局格式(match_parent,wrap_content或指定大小),将自身大小,和屏幕大小相比,设置一个不超过屏幕大小的宽高,以及对应模式。

对于其他View(包括ViewGroup),其确定是通过父布局的MeasureSpec和自身的布局参数LayoutParams。

这部分比较复杂。以下列图表表示不同的情况:

当子View的LayoutParams的布局格式是wrap_content,可以看到子View的大小是父View的剩余尺寸,和设置成match_parent时,子View的大小没有区别。为了显示区别,一般在自定义View时,需要重写onMeasure方法,处理wrap_content时的情况,进行特别指定。

从这里看出MeasureSpec的指定也是从顶层布局开始一层层往下去,父布局影响子布局。

可能关于MeasureSpec如何确定View大小还有些模糊,篇幅有限,没详细具体展开介绍,可以看这篇文章

View的测量流程:

三、Layout流程

测量完View大小后,就需要将View布局在Window中,View的布局主要通过确定上下左右四个点来确定的。

其中布局也是自上而下,不同的是ViewGroup先在layout()中确定自己的布局,然后在onLayout()方法中再调用子View的layout()方法,让子View布局。在Measure过程中,ViewGroup一般是先测量子View的大小,然后再确定自身的大小。

public void layout(int l, int t, int r, int b) {  

    // 当前视图的四个顶点
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight; // setFrame() / setOpticalFrame():确定View自身的位置
// 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //如果视图的大小和位置发生变化,会调用onLayout()
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { // onLayout():确定该View所有的子View在父容器的位置
onLayout(changed, l, t, r, b);
... }

上面看出通过 setFrame() / setOpticalFrame():确定View自身的位置,通过onLayout()确定子View的布局。 setOpticalFrame()内部也是调用了setFrame(),所以具体看setFrame()怎么确定自身的位置布局。

protected boolean setFrame(int left, int top, int right, int bottom) {
...
// 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
// 即确定了视图的位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}

确定了自身的位置后,就要通过onLayout()确定子View的布局。onLayout()是一个可继承的空方法。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

如果当前View就是一个单一的View,那么没有子View,就不需要实现该方法。

如果当前View是一个ViewGroup,就需要实现onLayout方法,该方法的实现个自定义ViewGroup时其特性有关,必须自己实现。

由此便完成了一层层的的布局工作。

View的布局流程:

四、Draw过程

View的绘制过程遵循如下几步:

①绘制背景 background.draw(canvas)

②绘制自己(onDraw)

③绘制Children(dispatchDraw)

④绘制装饰(onDrawScrollBars)

从源码中可以清楚地看出绘制的顺序。

public void draw(Canvas canvas) {
// 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
// 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制。
// 如果自定义的视图确实要复写该方法,那么需要先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
...
int saveCount;
if (!dirtyOpaque) {
// 步骤1: 绘制本身View背景
drawBackground(canvas);
} // 如果有必要,就保存图层(还有一个复原图层)
// 优化技巧:
// 当不需要绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
// 因此在绘制的时候,节省 layer 可以提高绘制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) { if (!dirtyOpaque)
// 步骤2:绘制本身View内容 默认为空实现, 自定义View时需要进行复写
onDraw(canvas); ......
// 步骤3:绘制子View 默认为空实现 单一View中不需要实现,ViewGroup中已经实现该方法
dispatchDraw(canvas); ........ // 步骤4:绘制滑动条和前景色等等
onDrawScrollBars(canvas); ..........
return;
}
...
}

无论是ViewGroup还是单一的View,都需要实现这套流程,不同的是,在ViewGroup中,实现了 dispatchDraw()方法,而在单一子View中不需要实现该方法。自定义View一般要重写onDraw()方法,在其中绘制不同的样式。

View绘制流程:

五、总结

从View的测量、布局和绘制原理来看,要实现自定义View,根据自定义View的种类不同,可能分别要自定义实现不同的方法。但是这些方法不外乎:onMeasure()方法,onLayout()方法,onDraw()方法。

onMeasure()方法:单一View,一般重写此方法,针对wrap_content情况,规定View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循环测量子View。

**onLayout()方法:**单一View,不需要实现该方法。ViewGroup必须实现,该方法是个抽象方法,实现该方法,来对子View进行布局。

**onDraw()方法:**无论单一View,或者ViewGroup都需要实现该方法,因其是个空方法

六、参考文章

  https://github.com/LRH1993/android_interview/blob/master/android/basis/custom_view.md

Android面试收集录12 View测量、布局及绘制原理的更多相关文章

  1. [译]Android view 测量布局和绘制的流程

    原文链接 创造优秀的用户体验是我们开发者的主要目标之一.为此, 我们首先要了解系统是如何工作的, 这样我们才可以更好的与系统配合, 从它的优点中获益, 规避它的缺陷. 之前关于Android渲染过程的 ...

  2. Android面试收集录 Android系统的资源+其他

    1.Android应用程序的资源是如何存储的,如何使用? res文件夹或者assets文件夹 res目录中的资源在R类中生成一个int变量,然后再布局文件中可以直接使用,在代码中,要getResour ...

  3. Android面试收集录 Android布局

    1.请说出Android中的五种布局,并介绍作用? FrameLayout(堆栈布局),层叠方式显示,类似于PhotoShop上的层叠图层. LinearLayout(线性布局),将视图以水平或者垂直 ...

  4. Android面试收集录 Android组件

    1.请说出Android SDK支持哪些方式显示富文本信息? 使用TextView组件可以显示富文本信息,如果要实现图文混排,需实现ImageGetter接口 使用WebView组件显示HTML页面 ...

  5. Android面试收集录11 Window+Activity+DecorView+ViewRoot之间的关系

    一.职能简介 Activity Activity并不负责视图控制,它只是控制生命周期和处理事件.真正控制视图的是Window.一个Activity包含了一个Window,Window才是真正代表一个窗 ...

  6. Android面试收集录6 事件分发机制

    转自:秋招面试宝典. 一. 基础认知 1.1 事件分发的对象是谁? 答:事件 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件). Touch事件相关细节(发 ...

  7. Android面试收集录4 Fragment详解

    1.什么是Fragment? 你可以简单的理解为,Fragment是显示在Activity中的Activity. 它可以显示在Activity中,然后它也可以显示出一些内容. 因为它拥有自己的生命周期 ...

  8. Android面试收集录1 Activity+Service

    1.Activity的生命周期 1.1.首先查看一下Activity生命周期经典图片. 在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期: onCreate()->on ...

  9. Android面试收集录 2D绘图与动画技术

    1.如何在Android应用程序的窗口上绘制图形? 继承View 实现View中的onDraw()方法 2.如何绘制圆,空心椭圆? canvas.drawArc或canvas.drawCircle方法 ...

随机推荐

  1. python模块详解 sys shutil

    sys模块 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0) sys.version 获取Python解释程序的版本信息 sy ...

  2. 再谈 Struts1.x 的运行机制

    1.Action类 execute 方法 ActionMapping 对应 <action path="user" type="myuser.UserAction& ...

  3. 3.tomcat

    1.进入网站http://www.apache.org 2.选择 3.关闭防火墙才可以让别人访问自己

  4. 关于ASP.NET页面事件的知识点

    ASP是动态服务器页面(ActiveServerPage)的英文缩写,是微软公司开发的代替CGI脚本程序的一种应用,它可以与数据库和其它程序进行交互,是一种简单.方便的编程工具.那么关于ASP.NET ...

  5. COGS 182. [USACO Jan07] 均衡队形

    ★★   输入文件:lineup.in   输出文件:lineup.out   简单对比时间限制:4 s   内存限制:128 MB 题目描述 农夫约翰的 N (1 ≤ N ≤ 50,000) 头奶牛 ...

  6. 前端高质量知识(三)-JS变量对象详解

    在JavaScript中,我们肯定不可避免的需要声明变量和函数,可是JS解析器是如何找到这些变量的呢?我们还得对执行上下文有一个进一步的了解. 在上一篇文章中,我们已经知道,当调用一个函数时(激活), ...

  7. 2019.03.14 ZJOI2019模拟赛 解题报告

    得分: \(100+100+0=200\)(\(T1\)在最后\(2\)分钟写了出来,\(T2\)在最后\(10\)分钟写了出来,反而\(T3\)写了\(4\)个小时爆\(0\)) \(T1\):风王 ...

  8. cudaMalloc和cudaMallocPitch

    原文链接 偶有兴趣测试了一下题目中提到的这两个函数,为了满足对齐访问数据,咱们平时可能会用到cudamallocPitch,以为它会带来更高的效率.呵呵,这里给出一段测试程序,大家可以在自己的机器上跑 ...

  9. 第6章 新建工程-寄存器版—零死角玩转STM32-F429系列

    第6章     新建工程—寄存器版 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fireg ...

  10. C#注释语句

    C#注释语句 注释就是在程序中标记.说明某个程序段的作用.注释语句不会被执行. 一.单行注释 // 这是一行注释 二.多行注释 /*    这是多行注释    第一行    第二行    ...... ...