Android View的绘制过程主要有三步:

  • 测量 Measure
  • 布局 Layout
  • 绘制 Draw

首先理解MeasureSpec的含义,然后跟踪ViewGroup的measure、layout、draw三个方法即可

view的绘制流程是我们在自定义View中通常会使用到的一个知识点,也是一个面试常问的点。简直是Android开发必备知识。

1、理解ViewRootImpl和DecorView两个类

DecorView我们相对比较熟悉,因为开发中就会不时的用到,它是整个Activity的顶层View,我们设置的布局文件都是它的子view。

而ViewRootImpl是连接WindowManager和DecorView的纽带,View的绘制三大流程都是通过ViewRootImpl来完成的。Activity创建后,会把DecorView添加到WindowManager中,同时会创建ViewRootImpl对象,使DecorView和ViewRootImpl建立联系,代码如下:

//代码地址:frameworks/base/core/java/android/view/WindowManagerGlobal.java  addView方法root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try {    root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {...}

View的绘制也是从ViewRootImpl的performTraversals方法开始的,然后调用view的measure、layout、draw方法,流程图如下:

2、理解MeasureSpec

在真正调用measure方法进行测量前,必须哟啊计算出MeasureSpec,然后才能用MeasureSpec去测出View的大小,那什么是MeasureSpec?

Android系统通过MeasureSpec来测量View的宽高,并且把测量模式和测量数据都放在MeasureSpec中,它由一个32位int值组成,其中高2位代表测量模式,也就是SepcMode,低30位代表测量值,也就是SpecSize,表示View宽或高的具体大小。MeasureSpec是View.java的一个内部类, MeasureSpec以及View的源码路径: /frameworks/base/core/java/android/view/View.java

public static class MeasureSpec {    private static final int MODE_SHIFT = 30;    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;    public @interface MeasureSpecMode {}    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;    public static int makeMeasureSpec(int size, int mode) {        if (sUseBrokenMakeMeasureSpec) {            return size + mode;        } else {            return (size & ~MODE_MASK) | (mode & MODE_MASK);        }    }    public static int getMode(int measureSpec) {        return (measureSpec & MODE_MASK);    }    public static int getSize(int measureSpec) {        return (measureSpec & ~MODE_MASK);    }}

MeasureSpec主要就提供了三个方法,上述的makeMeasureSpec方法相当于把mode和size组装在一起,形成一个MeasureSpec。

低30位表示宽或高的大小,那实际可以表示的大小为2^30 -1 ,是一个非常大的值了,完全够用。

高两位代表测量模式,按照00, 01, 10, 11四种组合来看,可以形成四种测量模式,实际上有三种测量模式:

  • UNSPECFIED : 此种模式表示父容器不对View有任何限制,要多大给多大,一般用于系统内部,仅表示一种测量状态;
  • EXACTLY : 表示父容器已经检测出view所需要的精确大小,此时View的最终大小就是SpecSize的值,它对应于LayoutParams中的match_parent或者设置的某个具体数值;
  • AT_MOST: 父容器制定了一个可用的最大数值,但是不知道子View会使用多少,而子View的大小不能超过父容器给的值,但具体也要看View的具体实现,这种模式对应于wrap_content;

3、MeasureSpec的生成过程

3.1 顶级View(即DecorView)的MeasureSpec生成过程

第2节提到, Android系统通过MeasureSpec来进行View的测量,并保存View的宽或高数据。正常情况下,我们在xml布局里面设置宽高或match_parent等, 或者在代码里通过LayoutParams来设置数据, 然后在系统测量View时,系统会将LayoutParams在父容器的约束下转换为对应的MeasureSpec,然后根据这个MeasureSpec测量子view的宽高。注意,是由LayoutParams和父View一起决定View的MeasureSpec。

但是,有一个特殊情况,那就是最顶层的DecorView, 它没有父view,它的MeasureSpec是由手机屏幕的尺寸和自身的LayoutParams来共同决定的。DecorView的MeasureSpec在ViewRootImpl中的measureHierarchy方法中创建:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

其中,desiredWindowWidth和desiredWindowHeight分别表示屏幕的宽高。 测量DecorView的宽高都调用了getRootMeasureSpec:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {    int measureSpec;    switch (rootDimension) {    case ViewGroup.LayoutParams.MATCH_PARENT:        // Window can't resize. Force root view to be windowSize.        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);        break;    case ViewGroup.LayoutParams.WRAP_CONTENT:        // Window can resize. Set max size for root view.        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);        break;    default:        // Window wants to be an exact size. Force root view to be that size.        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);        break;    }    return measureSpec;}

顶级View的测量属性中,测量大小就是屏幕大小,测量模式就是EXACTLY。

3.2普通View的MeasureSpec生成过程

普通View的测量,View的测量是通过ViewGroup传递过来的,因为每个view肯定都是存在于一个ViewGroup中, 先看ViewGroup中的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);}

以上代码主要分三步:

  • 1、通过父view宽的MeasureSpec, 然后加上横向的padding和margin,传入childView的宽, 然后算出childView的宽的测量模式MeasureSpec;
  • 2、通过父view高的MeasureSpec, 然后加上纵向的padding和margin,传入childView的高, 然后算出childView的高的测量模式MeasureSpec;
  • 3、通过前面计算的宽高的MeasureSpec,去调用childView的measure方法进行测量,得到真实宽高并记录的childView的MeasureSpec中;

进入getChildMeasureSpec方法查看一下是如何得到childView的测量模式的:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {    int specMode = MeasureSpec.getMode(spec);    int specSize = MeasureSpec.getSize(spec);    int size = Math.max(0, specSize - padding);    int resultSize = 0;    int resultMode = 0;    switch (specMode) {//注释1 通过父view的测量模式,做不同操作    // Parent has imposed an exact size on us    case MeasureSpec.EXACTLY:        if (childDimension >= 0) {//注释2 如果childView的宽高是一个具体的值, 如100dp            resultSize = childDimension;            resultMode = MeasureSpec.EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size. So be it.            resultSize = size;            resultMode = MeasureSpec.EXACTLY;//注释3 如果父view是EXACTLY,子view是match_parent, 则子view也是EXACTLY        } else if (childDimension == LayoutParams.WRAP_CONTENT) {            // Child wants to determine its own size. It can't be            // bigger than us.            resultSize = size;            resultMode = MeasureSpec.AT_MOST;        }        break;    // Parent has imposed a maximum size on us    case MeasureSpec.AT_MOST:        if (childDimension >= 0) {            // Child wants a specific size... so be it            resultSize = childDimension;            resultMode = MeasureSpec.EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size, but our size is not fixed.            // Constrain child to not be bigger than us.            resultSize = size;            resultMode = MeasureSpec.AT_MOST;        } else if (childDimension == LayoutParams.WRAP_CONTENT) {            // Child wants to determine its own size. It can't be            // bigger than us.            resultSize = size;            resultMode = MeasureSpec.AT_MOST;        }        break;    // Parent asked to see how big we want to be    case MeasureSpec.UNSPECIFIED:        if (childDimension >= 0) {            // Child wants a specific size... let him have it            resultSize = childDimension;            resultMode = MeasureSpec.EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size... find out how big it should            // be            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;            resultMode = MeasureSpec.UNSPECIFIED;        } else if (childDimension == LayoutParams.WRAP_CONTENT) {            // Child wants to determine its own size.... find out how            // big it should be            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;            resultMode = MeasureSpec.UNSPECIFIED;        }        break;    }    //noinspection ResourceType    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}

上述代码逻辑很简单,就是通过父view和子view的LayoutParams, 共同确定子view的测量模式MeasureSpec,总结如下:

  • 1、当View为固定宽高时,测量模式是EXACTLY模式,测量值就是布局参数中的大小。

  • 2、当View为WRAP_CONTENT时,测量模式是AT_MOST模式,测量值是父容器的剩余空间大小。

  • 3、当View为MATCH_PARENT时,测量值是父容器的剩余空间大小,测量模式分两种情况,如果父容器是EXACTLY模式,那就是EXACTLY模式,如果父容器是AT_MOST模式,那么View也是AT_MOST模式。

如第3.2小节开头的代码所展现的,当获取了子view的宽和高的MeasureSpec,就可以真正开始对view进行测量了:

 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

今天先写到这里,具体的measure、layout、draw过程的分析,请见下一篇博客Android View的测量、布局、绘制过程详解(下)

更多相关文章

  1. 2020.9.8 oppo Java开发(Android)一面面经
  2. Android(安卓)内功心法(1.5)——android常用设计模式之命令模式
  3. android 之view的测量和绘制(群英传读书笔记1)
  4. Android(安卓)Mvp模式详解(Kotlin篇)
  5. Android日记之2012/02/11——浅谈Iterator设计模式
  6. Android屏幕的大小、密度以及字符缩放比例——DisplayMetrics类
  7. Android检测手机中存储卡及剩余空间大小的方法(基于Environment,
  8. Android(安卓)- Toast字体修改
  9. Android(安卓)Activity启动机制流程和四种启动模式

随机推荐

  1. android中选中,获得焦点的,点击区别
  2. Android(安卓)和风天气SDK获取天气
  3. 2010.10.28———Android 02
  4. 【Android】FadingEdge
  5. 解读Android(安卓)3.2的新特性
  6. AndroidManifest.xml中一些权限配置
  7. Android NDK应用开发
  8. android和Myeclipse搭建环境
  9. 安卓设置文字自动滚动
  10. Cordova 3.x 源码分析(6) -- cordova.js本