热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

view的绘制流程复习

androidview绘制流程前言今天复习了view的绘制流程,看了几篇博客,百度搜索的前几篇写的大志差不多,沿着源码比,有点读不下去,然后又搜到了这篇全面升级Andro

android view 绘制流程


前言

今天复习了view的绘制流程,看了几篇博客,百度搜索的前几篇写的大志差不多,沿着源码比,有点读不下去,然后又搜到了这篇全面升级Android面试之View的绘制流程,看到这篇基本就把这个流程弄明白了。但是只是停留在基本明白,如果回过头来再想想,感觉还是很难组织起来,这就需要做个笔记和实践来加深印象了,所以写博客自己屡一下思路。那么我就通过自己的回忆写一写简单的流程。

读完一遍脑中的流程
  1. 首先映入脑海的应该是那张经典结构图,在从外到里结构是Window、Activity、DecorView、Actionbar、ContentView,ViewGroup、View。
  2. 然后是在哪启动绘制的呢?RootViewImpl里的performTraversals方法(),方法中调用view.measure(),layout(),draw()方法。
  3. measure方法中调用的是onMeasure方法。measure是不能重载的,onMeasure可以重载。onMeasure方法里调用setMeasuredDimension(),这个setMeasuredDimension可以自己定义尺寸,但是不推荐这样做,可能会影响子view的measure以及onlayout等。
  4. 上边是view里调用measure,ViewGroup调用measureChildren方法来测量它的子view。调用过程是measureChildren调用measureChild,measureChild里边先调用getChildMeasureSpec,然后获取child的宽高,再调用child的measure。将宽高传给child的measure,child的measure再调用onMeasure。这里边重点是getChildMeasureSpec的判断。
  5. 说起判断就要说下MeasureSpec,它一共32位,前2位(高2位)是表示Mode,后30位(低30位)表示size。getChildMeasureSpec方法里的判断主要就是判断父view的MeasureSpec的Mode。
  6. 那我们就主要看下getChildMreasureSpec()方法,代码如下

getChildMreasureSpec方法源码

 
 
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
		//首先将父view的Measure分解Mode和Size
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
		//定义size取0和specSize-padding的最大值
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;
		//判断父view的Mode为哪种类型
        switch (specMode) {
        //假如父view的Mode类型为EXACYLY(说明父view的大小是确切的数值,不管你子view多大不影响我)
        case MeasureSpec.EXACTLY:
			//如果child的params里width/height的值大于0,则返回的size结果就是子view的width/height值大小,
		    //Mode为EXACTLY
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
				//如果child的params里width/height的值是MATCH_PARENT,则返回的size大小就是0和specSize-padding的最大值
				//怎么理解上边这句话呢?因为父view是EXACTLY大小不变的,所以specSize是个固定值,然后子view是
				//MATCH_PARENT,则给你子view的实际尺寸就是我父view的尺寸减去你设置的padding值。
				//MODE为EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
				//如果child的params里width/height的值是WRAP_CONTENT,则返回的size大小就是0和specSize-padding的最大值
				//Mode为AT_MOST,这种情况就是子view自己宽高不确定,所以父view说,你别超过specSize-padding的尺寸就行了。
				//我们注意到size是Math.max(0, specSize - padding),这里就有一个问题,你的子view会填充父容器。
            } 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
		//假如父view的Mode类型为AT_MOST,说明父view的大小现在是不确定的,最大值为specSize
        case MeasureSpec.AT_MOST:
			//如果child的params里width/height的值大于0,则返回的size结果就是子view的width/height值大小,
		    //Mode为EXACTLY
            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
		//就是父view的size未被指定,子view随便吧,
        case MeasureSpec.UNSPECIFIED:
			//如果子view给出了确切的宽高,那子view的mode保证是EXACTLY的。
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
				//子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,
				//没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0
            } 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);
    }

因为调用getChildMeasureSpec方法是在measureChild方法里,代用代码如下:

    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    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方法参数spec就是child父view的MeasureSpec,padding就是child的左右或者上下padding,childDimension就是child.getLayoutParams(),LayoutParams返回的宽高只会有三种值,一个确定的值,MATCH_PARENT或WRAP_CONTENT。

知道这些就开始分析getChildMeasureSpec这个方法,根据自己的理解已经把注释写在源码里了,

其实概况起来就是,看child的LayoutParams是三种中的哪种,假如是确定的值,那么child的MeasureSpec就是确定的值+EXACTLY,如果child是MATCH_PARENT或WRAP_CONTENT,则需要看父view的MODE,如果父view的MODE是AT_MOST,说明子view也都是AT_MOST,size就是父view要求的最大size,如果父view的MODE是UNDEFINED,由于这种不确定性,子view也无法确定宽高,只能设为0;

接下来就是调用child的measure方法,然后measure调用onMeasure方法。基本测量就结束了。

关于layout和draw的理解,layout是不能被重载的,一般都是重写onLayout和onDraw方法,自定义ViewGroup一般重写onLayout方法,而view是没有子view的所以不需要重写onLayout方法,onDraw方法一般自定义ViewGroup和自定义View都会重写,ViewGroup一般重写onDraw来画子view间的分割线,View重写onDraw除了分隔线还会绘制其他。

上边的流程我是自己脑海想的,然后用as辅助查看了一下源码。写出来的。


思考

经过自己脑海中屡了一遍,然后再回过头来看网上文章做的分析。就弥补了许多残留的疑问。 
总体思路还需要再揣摩。 
好办法就是分析系统的自定义控件如Button,LinearLayout等控件,看系统是怎么写的。这样能更加体会view绘制流程在自定义控件里的应用。加油吧。



参考

Android View的绘制流程 
Android应用层View绘制流程与源码分析 
全面升级Android面试之View的绘制流程 
Android View面试难点解析


推荐阅读
author-avatar
琴瑟_0203
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有