Android View的绘制之measure过程初解。


前言

每个Android开发者,开发到一定程度后,都不可避免的涉及到各种自定义控件,各种性能优化的问题。
而学习和了解View的绘制过程,会对你的控件开发,性能优化等东西有很大的帮助。今天写这边博客,就是希望带大家从View和ViewGroup的基本的源码中,了解View的绘制过程。整个View的绘制涉及测量(measure),布局(layout),绘制(draw)等一系列过程。layout和draw相对比较简单,我后面的文章会继续跟进,今天这里我们来交流一下测量(measure)的过程。下面附上view绘制的流程图:


measure的目的

首先你应该要知道measure是用来做神马的。简单的来讲measure是为了对view的mMeasuredWidth和mMeasuredHeight这两个变量进行赋值,因此只要这两个变量赋值完成了,view的measure就完成了。目的就是那么简单。

measure的流程

要知道流程,我们必须知道源码中涉及的关键方法。对应的view以及viewGroup中关键方法如下。按调用顺序排列,为了篇幅清洁暂时没写入参。

  • View中:

    • public final void measue();
    • protected void onMeasure()
    • protected final void setMeasuredDimension()
  • ViewGroup中:

    • protected void measureChildren()
    • protected void measureChild()
    • protected void measureChildWithMargins()
    • public static int getChildMeasureSpec()

我们来依次的研究这些方法。先从关键的view开始。因为赋值是是在view中进行。viewGroup中其实是遍历ViewTree中的所有子view,再调用view中的measure方法实现赋值。

预备知识

在讲解之前给大家复习一下MeasureSpec(测量细则).
请各位还不是特别了解的看官移步:MeasureSpec介绍

View

进入正题,知道这些,让我们一起开始读源码。

第一个方法measure():

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        //若widthMeasureSpec或heightMeasureSpec就调用onMeasure去赋值。        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) {            // first clears the measured dimension flag            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;            resolveRtlPropertiesIfNeeded();            // 关键的调用方法。            onMeasure(widthMeasureSpec, heightMeasureSpec);            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {                throw new IllegalStateException("onMeasure() did not set the"                        + " measured dimension by calling"                        + " setMeasuredDimension()");            }            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;        }        mOldWidthMeasureSpec = widthMeasureSpec;        mOldHeightMeasureSpec = heightMeasureSpec;    }

Tips:mPrivateFlags相关的方法是一套固有逻辑不影响用户measure的过程,这里不展开讨论。大家略过这些方法就好。(其实是自己也不太清楚哈哈~~T.T)
看方法,measure其实非常简单,在发现widthMeasureSpec或 heightMeasureSpec发生变化时就去调用onMeasure()方法。大家可以发现measure方法定义成了final类型,设计者在设计之初就不希望这个方法被开发者修改。保证了android初始化view的原理是一致的。

第二个方法 onMeasure();

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

Tips:这是view的三个measure方法中唯一一个不是final的方法。因此我们在自定义空间中最多控制的也是这个方法。将你需要的widthMeasureSpec和heightMeasureSpec直接传过来就能调用setMeasuredDimension()方法进行赋值了。

第三种方法 setMeasuredDimension()

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

Tips:不难看出,mMeasuredWidth和mMeasuredHeight已经赋值成功,证明view的measure完成。是不是非常的简单。下面来了解一下ViewGroup的分发方法。


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);            }        }    }

遍历所有的viewTree下所有个子view.若子View未GONE就去measureChild(),测量对应的view.

方法二measureChild()

        protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        final LayoutParams lp = child.getLayoutParams();        //获取对应的子view测量出来的宽高。        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        //调用子view的measure方法对mMeasureWidth和mMeasureHeight进行赋值。完成测量        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

方法三measureChildWithMargins()

 //和方法二measureChild()完全一样的逻辑。只是宽高计算的时候加入了margin. 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);    }

和方法而逻辑雷同。关键是调用方法四的时候,传入的第二个参数int padding的数值加入了margin.

方法4getChildMeasureSpec()

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        //viewGroup的spec        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        //父控件的大小减去padding得到子View的大小。        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        //根据Mode以及childDimension获取子view对应的MeasureSpec。        switch (specMode) {        case MeasureSpec.EXACTLY:        //若childDimension有设置具体值,布局具体到具体值。        //若childDimension = match_parent。size赋予测出的大小,mode设置为EXACTLY。        //若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。            if (childDimension >= 0) {                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                // LayoutParams.MATCH_PARENT的大小                resultSize = size;                resultMode = MeasureSpec.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:        //若childDimension有设置具体值,布局具体到具体值。        //若childDimension = match_parent。size赋予测出的大小,mode设置为AT_MOST。        //若childDimension = wrap_content。size赋予测出的大小,mode设置为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:        //若childDimension有设置具体值,布局具体到具体值。        //若childDimension = match_parent。size设置为0,mode设置为UNSPECIFIED。        //若childDimension = wrap_content。size设置为0,mode设置为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 = 0;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = 0;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        //返回一个测量好的 MeasureSpec,包含resultSize以及resultMode。        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

Tips: getChildMeasureSpec是测量的关键步奏。他根据childDimension的情况,把测量出的大小和定好Mode。拼接好,并返回给子view去measure。我在代码中进行了必要的注解,大家可以打开源码,多读几遍加深影响。


后记

这里就是view以及viewGroup中measure最主要的方法介绍。了解了这些,当你再读LinearLayout等布局的measure时候就能水到渠成了。希望这篇文章能对大家能有所帮助。

我的下篇博客会针对view绘制的另两个过程 layout以及draw进行分享,
至于LinearLayout具体的计算绘制过程当我有一定心得后也会写博文和大家分享。

楼猪是工作一年的小虾。各位大神们发现问题希望能及时的拍砖。我会学马上改正,虚心学习的~

更多相关文章

  1. Android(安卓)九宫格解锁Demo--Android(安卓)进阶之路
  2. Android面试的一些总结
  3. Android(安卓)中的 Service 全面总结详解【下】
  4. Android接口回调机制
  5. android在java代码中动态添加组件及相关布局方法(LayoutParams)
  6. Android简单涂鸦以及撤销、重做的实现方法
  7. Android(安卓)webview加载https链接错误或无响应
  8. Android(安卓)NDK开发之Jni调用Java对象
  9. Android(安卓)中 Activity的内存泄漏,原因以及处理方法

随机推荐

  1. thinkPHP6框架基础知识
  2. 为什么考华为认证考华为认证有什么用?
  3. 华为认证改名HCNP改为HCIP
  4. 3.15 安装使用外部SQL Server的vCenter S
  5. KubeNode:阿里巴巴云原生容器基础设施运维
  6. IP网络性能测试工具——Renix Perf
  7. 华为认证考试题型分析备考指南
  8. wordpress日志审计插件audit-trail安装
  9. ]/bin/bash^M: bad interpreter: No such
  10. 华为认证考试时间查询|预约考试时间|报名