zoukankan      html  css  js  c++  java
  • Android 中View的绘制机制源代码分析 一

    尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113

    差点儿相同半年没有写博客了,一是由于工作比較忙,二是认为没有什么内容值得写,三是由于自己越来越懒了吧,只是近期我对Android中View的绘制机制有了一些新的认识。所以想记录下来并分享给大家。在之后的几篇博客中,我会给大家分享例如以下的内容:

    1、View中measure(),layout(),draw()函数运行过程分析,带领大家详细分析View的尺寸測量过程,位置计算,并终于绘制到UI上的过程

    2、以LinearLayout为例解说ViewGroup尺寸计算。位置计算,以及绘制过程

    3、更深层次的理解LayoutParams的意义

    4、LayoutInflater创建View的过程分析,详细分析inflate(int resource, ViewGroup root, boolean attachToRoot)方法中各个參数的意义

    掌握上面几个知识点对于自己定义View有很重要的意义的。并且据我所知自己定义View在面试过程中是必问知识点。

    以上内容都是Android中View系统比較重要的一些内容,View系统的功能主要包含用户输入消息到消息处理的整个过程,以及UI的绘制,用户输入消息以及消息处理的部分我之前也有写过几篇文章,如果读者用兴趣能够去了解下:

    Android 系统Touch事件传递机制 上:http://blog.csdn.net/yuanzeyao/article/details/37961997

    Android 系统Touch事件传递机制 下:http://blog.csdn.net/yuanzeyao/article/details/38025165

    Android 系统Key事件传递机制 上:http://blog.csdn.net/yuanzeyao/article/details/13630909

    Android 系统Key事件传递机制 下:http://blog.csdn.net/yuanzeyao/article/details/13631139


    由于涉及的内容比較多,所以我打算使用 多篇文章来解说上述内容,敬请期待。

    那么如今就開始学习View的measure过程吧。measure过程主要作用就是计算一个View的大小。这个事实上很好理解,由于不论什么一个View在绘制到UI上时,必须事先知道这个View的大小,不然是无法绘制的。

    平时我们在指定一个view的大小时。通常就是在xml文件里设置layout_width和layout_hegiht属性。这里我要提出一个问题:为什么View的宽度和高度相应的属性名前面有layout而不是直接叫width和height?先记住这个问题吧,等你看完本文的内容相信你就明确了。

    事实上measuer过程就将layout_width和layout_height这些属性变为详细的数字大小。


    当我们想要将一个xml文件显示到UI上时。通常就是将该xml文件的id传入到Activity的setContentView中去,事实上终于就会调用到ViewRoot的performTraversals方法,此方法承担了Android的View的绘制工作。这种方法代码许多。可是逻辑很简单,主要包含了三个阶段:

    第一个阶段就是我们今天要学习的measure,第二个阶段就是layout,第三个阶段就是draw,measure阶段就是得到每一个View的大小,layout阶段就是计算每一个View在UI上的坐标。draw阶段就是依据前面两个阶段的数据进行UI绘制。


    首先我们看看ViewRoot的performTraversals方法的部分代码(使用的2.3代码,选择2.3代码的原因是由于2.3的版本号逻辑比4.x版本号简单。并且主要逻辑还是一样的)

        private void performTraversals() {
            // Section one mView就是DecorView,
            final View host = mView;
    
    
    
    		//Section two
            int desiredWindowWidth;
            int desiredWindowHeight;
            int childWidthMeasureSpec;
            int childHeightMeasureSpec;
    
            ...
    
    
            Rect frame = mWinFrame;
            if (mFirst) {
                fullRedrawNeeded = true;
                mLayoutRequested = true;
    
                DisplayMetrics packageMetrics =
                    mView.getContext().getResources().getDisplayMetrics();
    			//Section three
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
    
                // For the very first time, tell the view hierarchy that it
                // is attached to the window.  Note that at this point the surface
                // object is not initialized to its backing store, but soon it
                // will be (assuming the window is visible).
               ...
    
            } else {
    			//Section four
                desiredWindowWidth = frame.width();
                desiredWindowHeight = frame.height();
                if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                    if (DEBUG_ORIENTATION) Log.v("ViewRoot",
                            "View " + host + " resized to: " + frame);
                    fullRedrawNeeded = true;
                    mLayoutRequested = true;
                    windowResizesToFitContent = true;
                }
            }
    
       
    
            boolean insetsChanged = false;
    
            if (mLayoutRequested) {
                // Execute enqueued actions on every layout in case a view that was detached
                // enqueued an action after being detached
                getRunQueue().executeActions(attachInfo.mHandler);
    
    
    			...
    			//Section five
                childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    
                // Ask host how big it wants to be
                if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot",
                        "Measuring " + host + " in display " + desiredWindowWidth
                        + "x" + desiredWindowHeight + "...");
    			//Section six
                host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                if (DBG) {
                    System.out.println("======================================");
                    System.out.println("performTraversals -- after measure");
                    host.debug();
                }
            }
    
            ....
        }

    上面的代码就是第一阶段的主要代码。请看代码中的Section one部分。这里定义了一个View 类型的变量host。它被赋值mView,这里我想说的仅仅是mView就是一个界面的DecorView。如果你还不熟悉DecorView能够看看我的另外一篇文章:

    《窗体的创建过程》,Section two分别定义了4个int 类型的变量,前面两个变量在Section three部分或者Section four部分赋值。通常第一次进来是在Section three里面进行赋值,也就是说desiredWindowWidth和disireWindowHeight各自是手机屏幕的宽和高(当然并不总是这种,这里我们仅仅用考虑简单的一种情况),在Section five部分分别对childWidthMeasureSpec和childHeightMeasureSpec进行赋值,这里调用了一个getRootMeasureSpec的方法。我们后面再分析它。在Setion six部分调用host.measure来计算View的大小。到这里performTraversals中mersure的调用过程就算结束了。可是getRootMeasureSpec和host的measure方法我们还不清楚它们究竟做了什么,以下就来分析这两个方法吧:

    先看看getRootMeasureSpec方法吧。

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

    看了实现之后,你是不是认为这种方法实现超简单,以getRootMeasureSpec(desiredWindowWidth。lp.width)为例,我们知道第一个參数就是屏幕的宽度。第二个參数是一个View的LayoutParams中的width属性,事实上这个參数是在Activity的

     void makeVisible() {
            if (!mWindowAdded) {
                ViewManager wm = getWindowManager();
                wm.addView(mDecor, getWindow().getAttributes());
                mWindowAdded = true;
            }
            mDecor.setVisibility(View.VISIBLE);
        }

    makeVisible方法传入的,makeVisible是在Activity的onResume里面调用,我们先不关心这个,我们关心的是这个lp是怎么创建的,我们看看getWindow.getAttributes()做了什么吧

      // The current window attributes.
        private final WindowManager.LayoutParams mWindowAttributes =
            new WindowManager.LayoutParams();

    通过源代码,找到Window的getAttributes方法,该方法返回mWindowAttributes值。我们看看WindowManager.LayoutParams这个类的空构造函数吧

       
            public LayoutParams() {
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                type = TYPE_APPLICATION;
                format = PixelFormat.OPAQUE;
            }

    看了构造函数后。我们发现layout_width和laout_height都是MATCH_PARENT。

    关于lp这个參数我们先看到这里,我们继续看getRootMeasureSpec这种方法,

    这里出现了一个MeasureSpec的陌生类,先看看MeasureSpec是何方圣神。MeasureSpec是定义在View中的一个内部类,这个类里面有几个比較重要的常量:

           private static final int MODE_SHIFT = 30;
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The parent has not imposed any constraint
             * on the child. It can be whatever size it wants.
             */
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The parent has determined an exact size
             * for the child. The child is going to be given those bounds regardless
             * of how big it wants to be.
             */
            public static final int EXACTLY     = 1 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The child can be as large as it wants up
             * to the specified size.
             */
            public static final int AT_MOST     = 2 << MODE_SHIFT;

    我们知道java中的int类型占用32位,任意这几个变量在内存中的表现形式例如以下:

    MODE_MASK:  11000000 00000000 00000000 00000000

    UNSPECIFIED: 000000000 00000000 00000000 00000000

    EXACTLY:         01000000 00000000 00000000 00000000

    AT_MOST:       10000000 00000000 00000000 00000000

    也就是说每一个高2位表示的model,第30位才真正表示尺寸的大小


    有了上面的基础之后,相信理解以下三个方法就不难了

    /**
             * Creates a measure specification based on the supplied size and mode.
             *
             * The mode must always be one of the following:
             * <ul>
             *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
             *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
             *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
             * </ul>
             *
             * @param size the size of the measure specification
             * @param mode the mode of the measure specification
             * @return the measure specification based on size and mode
             */
            public static int makeMeasureSpec(int size, int mode) {
                return size + mode;
            }
    
            /**
             * Extracts the mode from the supplied measure specification.
             *
             * @param measureSpec the measure specification to extract the mode from
             * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
             *         {@link android.view.View.MeasureSpec#AT_MOST} or
             *         {@link android.view.View.MeasureSpec#EXACTLY}
             */
            public static int getMode(int measureSpec) {
                return (measureSpec & MODE_MASK);
            }
    
            /**
             * Extracts the size from the supplied measure specification.
             *
             * @param measureSpec the measure specification to extract the size from
             * @return the size in pixels defined in the supplied measure specification
             */
            public static int getSize(int measureSpec) {
                return (measureSpec & ~MODE_MASK);
            }

    第一个方法makeMeasureSpec就是讲size和mode相加返回其结果。第二个getMode就是获取高2位的值。getSize就是获取低30位的值


    看明确了这里。我们就回到getRootMeasureSpec吧,我们知道lp.width属性通常有三种:match_parent(fill_parent),wrap_content,详细一个大小(如100dip),而这里通过我们上面的分析,知道宽和高均是match_parent。

    通过代码我们知道这三种情况相应的mode各自是:

    EXACTLY。AT_MOST。EXACTLY。也就是说math_parent和详细的大小(100dip)相应的都是EXACTLY。最后依据得到的mode和屏幕的宽度调用makeMeasureSpec方法得到一个int类型的值赋值给childWidthMeasureSpec,同理得到了childHeightMeasureSpec。并将这两个值传入measure中。以下我们就看看measure做了什么


    由于这里调用的是host的measure。而host事实上是一个FrameLayout,所以我不打算继续使用这个样例将View的測量过程了,可是ViewGroup是没有改写measure的,所以事实上调用的还是View的measure方法。measure方法的源代码例如以下:


        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
    
                // first clears the measured dimension flag
                mPrivateFlags &= ~MEASURED_DIMENSION_SET;
    
                if (ViewDebug.TRACE_HIERARCHY) {
                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
                }
    
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
    
                // flag not set, setMeasuredDimension() was not invoked, we raise
                // an exception to warn the developer
                if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                    throw new IllegalStateException("onMeasure() did not set the"
                            + " measured dimension by calling"
                            + " setMeasuredDimension()");
                }
    
                mPrivateFlags |= LAYOUT_REQUIRED;
            }
    
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
        }

    我们看到measure方法事实上是final的,所以ViewGroup是无法改写此方法的。通常一个详细的ViewGroup都是改写onMeasure方法。这点你能够去看看LinearLayout和FrameLayout。他们在onMeasure方法里面都间接调用了ViewGroup的measureChildWithMargins方法,今天我们就以measureChildWithMargins这种方法为入口分析View的測量过程。

    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);
        }
    这里我们简化下情况,我们如果ViewGroup里面全部的孩子都是View,没有ViewGroup。

    以下我们分三步来分析measureChildWithMargins方法:

    1、获取孩子的LayoutParams

    2、调用getChildMeasureSpec方法得到孩子的measureSpec(包含widthSpec和heightSpec)

    我们看看getChildMeasureSpec做了什么,先看看它的几个參数。以获取孩子的widthSpec为例 。第一个參数是ViewGroup的widthSpec,第二个參数是ViewGroup已经被使用的width。第三个是lp.width,接下来看看源代码:

       */
        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) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    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;
                } 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 = 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;
            }
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }

    相信有了前面的基础,看这段代码应该很easy。事实上就是依据ViewGroup的mode和size以及lp.width的值来创建View的measureSpec。

    如今知道我前面提的问题的答案了吗,为什么width前面要加一个layout。由于子View的大小时自己(子View)和ViewGroup(父View)共同决定的。


    回到measureChildWithMargins 看第三步:调用了child.measure。并且參数就是第二步中得到的,另外注意这个child就是一个普通的View(由于我们已经如果ViewGroup里面没有ViewGroup,仅仅有View)


    由于是View调用measure,所以measure中调用onMeasure也是View中的,我们看看View的onMeasuere方法吧


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


    这里出现了一个重要的方法getDefaultSize,其代码例如以下:

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

    该方法依据measureSpec的mode决定返回值是size还是specSize。

    在多数情况下载mode是AT_MOST或者EXACTLY,(UNSPECIFIED通常出如今我们为了获得某个view的大小时,调用此view.measure(0,0)的时候出现.),在onMeasure中会调用setMeasuredDimension()方法将得到的大小分别赋值给mMeasuredWidth,mMeasuredHeight。从而View的大小就測量完毕了。

    代码例如以下:

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

    到这里View的測量过程告一段落了,至于ViewGroup的測量过程在下篇文章中使用LinearLayout分析一下吧。


    Android 中View的绘制机制源代码分析 二 已经公布,敬请关注!


  • 相关阅读:
    LRU算法简介
    linux下安装nginx+php+mysql环境 详细教程
    CentOS 6.6编译安装Nginx1.6.2+MySQL5.6.21+PHP5.6.3
    unicode 格式 转汉字
    js 操作cookie
    哈希函数
    php商城秒杀活动
    php 栈、 出栈、入栈
    php单例模式
    封装PHP增删改查方法
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/7110977.html
Copyright © 2011-2022 走看看