承接上文——Window、DecorView、ViewRootImp详解

我们打开一个Activity后,在ActivityThread中 的performLaunchActivity方法中,回调Activity onCreate()之前 会先调用Activity.attach(),这个方法中初始化了PhoneWindow和WindowManager对象。然后在resume之前,会将DecorView和WindowManager关联,WindowManager的具体实现类WindowManagerImpl把addView的逻辑交给WindowManagerGlobal处理。WindowManagerGlobal最终会创建ViewRootImpl对象,关联DecorView对象,最终再向WMS申请创建窗口,执行到performTraversals,开始从上到下遍历整个视图树的绘制流程。

ViewRootImpl.performTraversals()

performTraversals()的源码很长,核心代码就下面三个步骤。

private void performTraversals() {......int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);......performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);......performLayout(lp, mWidth, mHeight);......performDraw();
}

所以,一个完整的绘制流程包括measure、layout、draw三个步骤。

每个View负责绘制自己,而ViewGroup还要负责通知自己的子View进行绘制。

MeasureSpec

MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小,MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配。

MeasureSpec简单点来说是用来概括从父布局传递给子View的布局要求。

SpecMode有三类,

UNSPECIFIED

父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态

EXACTLY

父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。对应于LayoutParams中的match_parent和具体数值两种模式。

AT_MOST

父容器制定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看View的具体表现。他对应于LayoutParams中的wrap_content。

那么MeasureSpec是如何确定的?

对于DecorView,是通过屏幕的大小和自身的布局参数LayoutParams。
根据LayoutParams的布局格式,match_parent、wrap_content或者指定大小,将自身的大小和屏幕大小相比,设置一个不超过屏幕大小的宽高和对应模式。

对于普通View,其确定是通过父布局的MeasureSpec和自身的布局参数决定的。

  1. 当View采用固定尺寸时,不管父容器的MeasureSpec是什么,View的SpecMode一定是EXACTLY并且遵循LayoutParams中的大小。

  2. 当View的宽高是match_parent时,如果父容器SpecMode为EXACTLY,那么View也是EXACTLY并且大小是父容器的剩余空间;
    如果父容器是AT_MOST,那么View也是AT_MOST,并且大小不会超过父容器的剩余空间。

  3. 当View的宽高是wrap_content时,不管父容器是EXACTLY或者AT_MOST,view总是AT_MOST并且大小不能超过父容器的空间。

Measure流程

measure要分情况来看,如果是原始的View,那么通过measure就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还回遍历去调用所有子元素的measure方法,各个子元素再递归执行这个流程。

1. View的measure过程

View的measure过程由其measure方法完成,measure方法是一个final方法,所以子类不能override。在View的measure中会去调用View的onMeasure,实际测量工作是在onMeasure中实现的。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}

onMeasure方法中setMeasuredDimension会给View设置宽高的测量值。
getDefaultSize方法的逻辑也很简单,
对于AT_MOST和EXACTLY两种情况,返回的大小就是measureSpec的specSize。
对于UNSPECFIC,会根据有没有设置背景,返回android:minWidth属性或者 minWidth和背景的原始宽度中较大的值。

这里仅仅是测量大小,View的最终大小会在layout阶段确定。但是,几乎所有情况测量大小和最终大小是相等的。

如果View在布局中使用的wrap_content,那么SpecMode是AT_MOST,在这种情况下,getDefault返回的也是specSize,这种情况下和使用match_parent是完全一致,所以在自定义View中,需要针对wrap_content 在onMeasure中做特殊处理。

2. ViewGroup的measure过程

对于ViewGroup,除了完成自己的measure以外,还要遍历所有子元素的measure,各个子元素再去递归这个过程。和View不同的是,ViewGroup是个抽象类,没有重写View的onMeasure,ViewGroup提供了一个measureChildren的方法。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {final int size = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < size; ++i) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {measureChild(child, widthMeasureSpec, heightMeasureSpec);}}
}

可以看到ViewGroup在measure时,会对子元素遍历进行measure。

 protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

measureChild就是取出子元素的LayoutParams,结合父View的MeasureSpec,创建子View的measureSpec对象,将这个对象传递给View的measure方法进行测量。

ViewGroup没有定义自己测量的具体过程,每种ViewGroup按照自己的布局特性有不同的实现,如LinearLayout、RelativeLayout等等,这里不做详细分析。

3. 如何在Activity启动时获得View的宽高

如果要在Activity启动时获取View的宽高,由于View的measure过程和Activity生命周期不是同步执行的,无法保证Activity生命周期回调时View已经测量完毕。

  1. Activity/View#onWindowFocusChanged:回调时View已经初始化完毕。
  2. View.post(Runnable):通过pst将一个runnable投递到消息队列尾部,等Looper调用的时候,View也初始化好了
  3. ViewTreeObserver:当View树状态发生变化或者View树内部View的可见性发生改变时,会回调onGlobalLayoutListener
  4. 手动调用view.measure

Layout流程

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

ViewGroup先在layout()中确定自己的布局,然后再onLayout中在调用子View的layout。
在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时其特性有关,必须自己实现。

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

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的绘制流程-measure、layout、draw相关推荐

  1. Android之View的绘制流程解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 自定义View在Android中占据着非常重要的地位,因此了解View的 ...

  2. Android O: View的绘制流程(三):布局和绘制

    前一篇文章Android O: View的绘制流程(二):测量中,  我们分析了View的测量流程.  当View测量完毕后,就要开始进行布局和绘制相关的工作,  本篇文章就来分析下这部分流程. 一. ...

  3. Android View的绘制流程简述 Android自定义View(一)

    1 Android的UI管理系统层级关系 如上图所示,这就是Android的UI管理系统的层级关系. 1.1 当一个应用启动的时候,会启动一个主Activity,然后Activity会创建出一个窗口系 ...

  4. 红橙Darren视频笔记 view的绘制流程(下)基于API27

    关于View的测量代码是如何执行到的以及performMeasure的具体流程可以参见上一篇笔记 之前忘了说明 view的绘制流程里面用到的设计模式是模板设计模式 可以参考这篇文章 https://b ...

  5. android自定义view流程,Android 自定义View--从源码理解View的绘制流程

    前言 在Android的世界里,View扮演着很重要的角色,它是Android世界在视觉上的具体呈现.Android系统本身也提供了很多种原生控件供我们使用,然而在日常的开发中我们很多时候需要去实现一 ...

  6. 【Android面试】View的绘制流程

    目录 View的绘制流程简介 Activity和window和view 的关系 Activity和Window是什么时候建立联系的呢? ViewRootImpl View的绘制流程总结 View的绘制 ...

  7. Android View的绘制流程(1) -- 测量onMeasure

    鉴于是首篇讲解自定义view流程,之前也在网上搜了一些博主的博客看了看,都是大同小异,今天抽时间自己总结一下,分享一下自己的感悟,也算是一篇笔记. (本篇为开头篇,稍微讲述一下有关的东西) View的 ...

  8. Android自定义View系列之详解View的绘制流程

    目录 一.开场白 二.View的绘制流程 2.1测量的过程 2.2布局的过程 2.3绘制的过程 一.开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout.Fr ...

  9. Android O: View的绘制流程(二):测量

    在前一篇博客Android O: View的绘制流程(一): 创建和加载中,  我们分析了系统创建和加载View的过程,这部分内容完成了View绘制的前置工作. 本文开始分析View的测量的流程. 一 ...

最新文章

  1. 描述一下Spring框架的作用和优点?
  2. ipc620中文版最新版本_(一)Windows10 家庭中文版Docker安装 搭建docker开发环境
  3. 你不一定知道的几个前端小知识
  4. oracle 10g 更换ocr,Oracle10g RAC在线更换OCR votedisk
  5. nvm install node没反应_前端开发,你要懂得Node.js的安装和使用方法
  6. WPF学习(3) – WPF控件
  7. 蓝桥杯 BASIC-9 基础练习 特殊回文数
  8. 企业常用的RPC框架比较
  9. 深入理解Nginx~正常运行的配置项
  10. jdk帮助文档在哪_jdk帮助文档在哪里下载
  11. 体系结构14_控制相关的动态解决技术
  12. 高中数学培训高一数学提分技巧
  13. lookup无序查找_excel无序查询 使用LOOKUP函数实现无序查询
  14. origin软件画流程图_免费的网络拓扑流程图绘制软件(PaceStar LanFlow)
  15. 蚂蚁金服凭啥估值超1500亿美元?一文看懂6大核心板块
  16. 线程和进程总结(无坑版)
  17. 单元格中公式结果为0如何不显示0符号?
  18. 一笔画 java_NYOJ42 一笔画问题
  19. 计组 | 寻址范围的概念与数据寄存器的位数
  20. against fate

热门文章

  1. 基于 React hooks + Typescript + Cesium 实现坐标拾取功能组件
  2. 终于,狂神说SSM及SpringBoot系列文章完更!!!
  3. 《人生的智慧》——人的基本划分
  4. 应用Abaqus有限元软件中的cohesive单元模拟压头侵入地层随机断裂过程
  5. 电池内阻及其测量方法
  6. LTE RSRQ 报告值与 RSRQ 质量换算关系
  7. python文件打包技术免费教程
  8. JDK 16 新特性,正式发布!程序员:追不上了...
  9. 自动驾驶 Apollo 源码分析系列,感知篇(二):Perception 如何启动?
  10. Python实现京东抢秒杀