一、绘制流程源码路径

1、Activity加载ViewRootImpl

ActivityThread.handleResumeActivity()
--> WindowManagerImpl.addView(decorView, layoutParams)
--> WindowManagerGlobal.addView()

2、ViewRootImpl启动View树的遍历

ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals()(performMeasure、performLayout、performDraw)

二、View绘制流程

1、measure

(1)MeasureSpec是什么?

重写过onMeasure()方法都知道,测量需要用到MeasureSpec类获取View的测量模式和大小,那么这个类是怎样存储这两个信息呢?

留心观察的话会发现,onMeasure方法的两个参数实际是32位int类型数据,即:

00 000000 00000000 00000000 00000000

而其结构为 mode + size ,前2位为mode,而后30位为size。

==> getMode()方法(measureSpec --> mode):
private static final int MODE_SHIFT = 30;
// 0x3转换为二进制即为:11
// 左移30位后:11000000 00000000 00000000 00000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static int getMode(int measureSpec) {
// 与MODE_MASK按位与运算后,即将低30位清零,结果为mode左移30位后的值
return (measureSpec & MODE_MASK);
}

getSize()方法同理。

==> makeMeasureSpec()方法(mode + size --> measureSpec):
public static int makeMeasureSpec(
@IntRange(from = 0,
to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}

这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:

01000000 00000000 00000000 00000000 |
00001000 00001011 11110101 10101101 =
01001000 00001011 11110101 10101101

二进制计算规则可参考:https://www.cnblogs.com/joahyau/p/6420619.html

==> 测量模式:
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;

UNSPECIFIED:父容器不对View作任何限制,系统内部使用。

EXACTLY:精确模式,父容器检测出View大小,即为SpecSize;对应LayoutParams中的match_parent和指定大小的情况。

AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出这个值;对应wrap_content。

(2)ViewGroup的测量流程

回到ViewRootImpl的performMeasure方法,这里传入的参数为顶层DecorView的测量规格,其测量方式为:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

match_parent和具体数值大小为EXACTLY模式,wrap_content则为AT_MOST模式。

往下走,performMeasure方法中调用了DecorView的onMeasure方法,而DecorView继承自FrameLayout,可以看到FL的onMeasure方法中调用了measureChildWithMargins方法,并传入自身的测量规格:

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

即测量子控件的大小,测量规则详情可看getChildMeasureSpec方法,总结如下:

childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize
match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

回到onMeasure方法,测完子控件之后,ViewGroup会经过一些计算,得出自身大小:

// 加上padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // 检查是否小于最小宽度、最小高度
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // 检查Drawable的最小高度和宽度
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
} setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

综上,ViewGroup的测量需要先测量子View的大小,而后结合padding等属性计算得出自身大小。

(3)View的测量流程
View.performMeasure()
-->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
-->setMeasuredDimension(int measuredWidth, int measuredHeight)
-->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)

可以看到setMeasuredDimensionRaw()方法:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 存储测量结果
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight; // 设置测量完成的标志位
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

View不需要考虑子View的大小,根据内容测量得出自身大小即可。

另外,View中的onMeasure方法中调用到getDefaultSize方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
} 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;
}

这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义View一定要重写onMeasure方法的原因。

2、layout

布局相对测量而言要简单许多,从ViewRootImpl的performLayout方法出发,可以看到其中调用了DecorView的layout方法:

// 实则为DecorView的left, top, right, bottom四个信息
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

进入layout方法,发现l、t、r、b被传递到了setFrame方法中,并设置给了成员变量:

mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;

所以,布局实际为调用View的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onLayout方法,进入后发现为空方法。因而查看FrameLayout的onLayout方法:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
} void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount(); // 省略 for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // 省略 child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}

可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。

综上,布局实际为调用layout方法设置View位置,ViewGroup则需要另外实现onLayout方法摆放子控件。

3、draw

(1)绘制过程入口
ViewRootImpl.performDraw()
-->ViewRootImpl.draw()
-->ViewRootImpl.drawSoftware()
-->View.draw()
(2)绘制步骤

进入到View的draw方法中,可以看到以下一段注释:

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/

结合draw方法的源码,绘制过程的关键步骤如下:

==> 绘制背景:drawBackground(canvas)

==> 绘制自己:onDraw(canvas)

==> 绘制子view:dispatchDraw(canvas)

==> 绘制滚动条、前景等装饰:onDrawForeground(canvas)

Android UI绘制流程及原理的更多相关文章

  1. Android UI 绘制过程浅析(五)自定义View

    前言 这已经是Android UI 绘制过程浅析系列文章的第五篇了,不出意外的话也是最后一篇.再次声明一下,这一系列文章,是我在拜读了csdn大牛郭霖的博客文章<带你一步步深入了解View> ...

  2. Android View绘制流程

    框架分析 在之前的下拉刷新中,小结过触屏消息先到WindowManagerService(Wms)然后顺次传递给ViewRoot(派生自Handler),经decor view到Activity再传递 ...

  3. Android视图绘制流程完全解析,带你一步步深入了解View(二)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/16330267 在上一篇文章中,我带着大家一起剖析了一下LayoutInflater ...

  4. Android View绘制和显示原理简介

    现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何在屏幕上显示的则是基础中的基础,就让我们一起看看小小屏幕中大大的学问.这也是我下篇文章--<Android应用流畅度测 ...

  5. 一篇文章教你读懂UI绘制流程

    最近有好多人问我Android没信心去深造了,找不到好的工作,其实我以一个他们进行回复,发现他们主要是内心比较浮躁,要知道技术行业永远缺少的是高手.建议先阅读浅谈Android发展趋势分析,在工作中, ...

  6. android view绘制流程 面试

    一.view树的绘制流程 measure--->layout--->draw measure 1.ViewGroup.LayoutParams 指定部件的长宽 2.MeasureSpec ...

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

    前言 前几篇文章,笔者分别讲述了DecorView,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程——绘制流程.测量流程决定了View的大小,布局流程决定了View的位 ...

  8. Android View 绘制流程

    Android 中 Activity 是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当 Activity 启动时,我们会通过 setContentView 方法来设 ...

  9. Android UI 绘制过程浅析(四)draw过程

    前言 draw是绘制View三个步骤中的最后一步.同measure.layout一样,通常不对draw本身进行重写,draw内部会调用onDraw方法,子类View需要重写onDraw(Canvas) ...

随机推荐

  1. mysql的配置和启动命令

    一.mysql配置文件在linux系统下的位置 使用命令查询位置: 1.找到安装位置 which mysql  -> /usr/bin/mysql 2.接下来就可以针对这个目录通过一些命令查看配 ...

  2. memecached存放session数据

    memcached存放session 1.session数据需要频繁调用. 2.session数据不需要永久性的保存在服务端. 3.在集群中,可以将session存放在memcached中或者是在数据 ...

  3. 【Linux】一步一步学Linux——VMware Workstation 15 Pro安装图解教程(06)

    目录 00. 目录 01. VMware Workstation Pro15下载 02. VMware Workstation Pro15安装 03. VMware Workstation Pro 1 ...

  4. asp.net core 2.2 生产环境直接更新View页面并立即生效

    有的时候我们会直接在生产环境上更新页面文件,比如更改了JS代码,CSS代码或页面的文案,布局等.这种没有改到后台代码的情况一般就是直接发布页面文件了,在asp.net core 2.2以前的版本(没有 ...

  5. Apple官文中的KVO 与 FBKVOController

    前言 本文将主要介绍以下内容: 详细列出Apple官文中KVO的注意事项(Apple KVO相关的引用皆摘自Apple官文). 介绍FBKVOController,以及它如何避免系统提供的KVO坑点. ...

  6. 小白教程 Java web maven项目Windows下部署阿里云服务器全过程(买服务器,打包项目,连接服务器,配置服务器,部署项目)

    -------------首先:购买服务器---------------- Step1: 在https://www.aliyun.com/?utm_content=se_1043015注册登录阿里云 ...

  7. java中几个常见的问题

    1.正确使用equals方法 Object的equals方法容易抛出空指针异常,应使用常量或确定有值的对象来调用equals方法 例如: //不能使用一个值为null的引用类型变量来调用非静态方法,否 ...

  8. Atlassian In Action-Jira之核心配置(二)

    道生一,一生二,二生三,三生万物. --<道德经> 如果说第一节的指导思想是管理之"道",那我们本节的核心配置就是Jira系统之"道"了.有了核心配 ...

  9. springcloud-注册中心快速构建

    1. 场景描述 springcloud提供了一整套可行的构建分布式系统的方案,使的企业/开发人员能够快速沟通分布式系统,今天快速构建下springcloud的注册中心Eureka. 2. 解决方案 2 ...

  10. idea的安装与配置及基本用法

    Intellij IDEA 确实使用更加方便,由于目前只用到maven项目,所以此处只记录maven项目的配置. 一.配置idea前准备: 1.下载idea安装包.jdk安装包.maven安装包.gi ...