zoukankan      html  css  js  c++  java
  • [Android学习笔记]View的measure过程学习

    View从创建到显示到屏幕需要经历几个过程:

    measure -> layout -> draw

    measure过程:计算view所占屏幕大小
    layout过程:设置view在屏幕的位置
    draw过程:绘制view


     

    继承自view的控件的measure过程

    view.measure(int,int)方法有什么作用?


    view.measure(int,int)用于询问(或称为设置)当前view需要(想要)占用多大得空间。
    简单理解为,为view申请两个int值大小的尺寸的控件

    View.java

    /***
         * <p>
         * This is called to find out how big a view should be. The parent
         * supplies constraint information in the width and height parameters.
         * </p>
         *
         * <p>
         * The actual mesurement work of a view is performed in
         * {@link #onMeasure(int, int)}, called by this method. Therefore, only
         * {@link #onMeasure(int, int)} can and must be overriden by subclasses.
         * </p>
         *
         *
         * @param widthMeasureSpec Horizontal space requirements as imposed by the
         *        parent
         * @param heightMeasureSpec Vertical space requirements as imposed by the
         *        parent
         *
         * @see #onMeasure(int, int)
         */
        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;
        }

    从源码看,我们需要关注的几个方法有:
    onMeasure(int,int);                 //如有需求,需重写
    setMeasuredDimension(int,int);//保存结果
    getDefaultSize(int,int);         //原逻辑
    getSuggestMinimumHeight(); //原逻辑
    getSuggestMinimumWidth();  //原逻辑


    MeasureSpec类
    MeasureSpec.getMode();
    MeasureSpec.getSize();
    MeasureSpec.UNSPECIFIED
    MeasureSpec.AT_MOST
    MeasureSpec.EXACTLY


    回过头说,什么时候会用到view.measure(int,int)?
    通常情况下,很少显式调用view.measure(int,int)方法,除非是需要根据需求变更view的大小和位置
    更多调用view.measure(int,int)方法的是android框架本身,当绘制创建控件时候,android框架会调用此方法
    询问view需要多大的空间。

    说到这,可以知道两个参数widthMeasureSpec,heightMeasureSpec的作用了。
    measure方法中的两个参数是父类传递过来给当前view的一个建议值,即想把当前view的尺寸
    设置为宽widthMeasureSpec,高heightMeasureSpec

    回到View源码中可以看到,在调用measure(int,int)之后,如果与old值不相等则会回调view的onMeasure(int,int)方法
    进行具体实质性的view大小的计算.
    (所以,如果你想对自己的view进行一些定制,则需要重写view的onMeasure(int,int)方法,把定制代码写在此方法中)
    (注意:从measure源码可以得知,重写onMeasure方法时候记得要调用setMeasuredDimension(int,int),否则在measure中会抛出IllegalStateException异常)

        /***
         * <p>
         * Measure the view and its content to determine the measured width and the
         * measured height. This method is invoked by {@link #measure(int, int)} and
         * should be overriden by subclasses to provide accurate and efficient
         * measurement of their contents.
         * </p>
         *
         * <p>
         * <strong>CONTRACT:</strong> When overriding this method, you
         * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
         * measured width and height of this view. Failure to do so will trigger an
         * <code>IllegalStateException</code>, thrown by
         * {@link #measure(int, int)}. Calling the superclass'
         * {@link #onMeasure(int, int)} is a valid use.
         * </p>
         *
         * <p>
         * The base class implementation of measure defaults to the background size,
         * unless a larger size is allowed by the MeasureSpec. Subclasses should
         * override {@link #onMeasure(int, int)} to provide better measurements of
         * their content.
         * </p>
         *
         * <p>
         * If this method is overridden, it is the subclass's responsibility to make
         * sure the measured height and width are at least the view's minimum height
         * and width ({@link #getSuggestedMinimumHeight()} and
         * {@link #getSuggestedMinimumWidth()}).
         * </p>
         *
         * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         * @param heightMeasureSpec vertical space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         *
         * @see #getMeasuredWidth()
         * @see #getMeasuredHeight()
         * @see #setMeasuredDimension(int, int)
         * @see #getSuggestedMinimumHeight()
         * @see #getSuggestedMinimumWidth()
         * @see android.view.View.MeasureSpec#getMode(int)
         * @see android.view.View.MeasureSpec#getSize(int)
         */
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }

    其中涉及到三个方法:
    setMeasuredDimension(int,int)
    getDefaultSize(int,int)            //原实现
    getSuggestedMinimumHeight();//原实现
    getSuggestedMinimumWidth(); //原实现

    setMeasuredDimension:

        /***
         * <p>
         * Measure the view and its content to determine the measured width and the
         * measured height. This method is invoked by {@link #measure(int, int)} and
         * should be overriden by subclasses to provide accurate and efficient
         * measurement of their contents.
         * </p>
         *
         * <p>
         * <strong>CONTRACT:</strong> When overriding this method, you
         * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
         * measured width and height of this view. Failure to do so will trigger an
         * <code>IllegalStateException</code>, thrown by
         * {@link #measure(int, int)}. Calling the superclass'
         * {@link #onMeasure(int, int)} is a valid use.
         * </p>
         *
         * <p>
         * The base class implementation of measure defaults to the background size,
         * unless a larger size is allowed by the MeasureSpec. Subclasses should
         * override {@link #onMeasure(int, int)} to provide better measurements of
         * their content.
         * </p>
         *
         * <p>
         * If this method is overridden, it is the subclass's responsibility to make
         * sure the measured height and width are at least the view's minimum height
         * and width ({@link #getSuggestedMinimumHeight()} and
         * {@link #getSuggestedMinimumWidth()}).
         * </p>
         *
         * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         * @param heightMeasureSpec vertical space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         *
         * @see #getMeasuredWidth()
         * @see #getMeasuredHeight()
         * @see #setMeasuredDimension(int, int)
         * @see #getSuggestedMinimumHeight()
         * @see #getSuggestedMinimumWidth()
         * @see android.view.View.MeasureSpec#getMode(int)
         * @see android.view.View.MeasureSpec#getSize(int)
         */
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }

     

    getDefaultSize:

        /***
         * Utility to return a default size. Uses the supplied size if the
         * MeasureSpec imposed no contraints. Will get larger if allowed
         * by the MeasureSpec.
         *
         * @param size Default size for this view
         * @param measureSpec Constraints imposed by the parent
         * @return The size this view should be.
         */
        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;
        }

    getSuggestedMinimumHeight:

        /***
         * Returns the suggested minimum height that the view should use. This
         * returns the maximum of the view's minimum height
         * and the background's minimum height
         * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
         * <p>
         * When being used in {@link #onMeasure(int, int)}, the caller should still
         * ensure the returned height is within the requirements of the parent.
         *
         * @return The suggested minimum height of the view.
         */
        protected int getSuggestedMinimumHeight() {
            int suggestedMinHeight = mMinHeight;
    
            if (mBGDrawable != null) {
                final int bgMinHeight = mBGDrawable.getMinimumHeight();
                if (suggestedMinHeight < bgMinHeight) {
                    suggestedMinHeight = bgMinHeight;
                }
            }
    
            return suggestedMinHeight;
        }

    getSuggestedMinimumWidth:

        /***
         * Returns the suggested minimum width that the view should use. This
         * returns the maximum of the view's minimum width)
         * and the background's minimum width
         *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
         * <p>
         * When being used in {@link #onMeasure(int, int)}, the caller should still
         * ensure the returned width is within the requirements of the parent.
         *
         * @return The suggested minimum width of the view.
         */
        protected int getSuggestedMinimumWidth() {
            int suggestedMinWidth = mMinWidth;
    
            if (mBGDrawable != null) {
                final int bgMinWidth = mBGDrawable.getMinimumWidth();
                if (suggestedMinWidth < bgMinWidth) {
                    suggestedMinWidth = bgMinWidth;
                }
            }
    
            return suggestedMinWidth;
        }

    setMeasuredDimension(int,int)方法
    保存了传入的建议尺寸

    getDefaultSize(int,int)方法
    获取一个默认值

    getSuggestedMinimumHeight()方法
    获取此控件的最小可用值(如果设置了android:minHeight

    getSuggestedMinimumWidth()方法
    获取此控件的最小可用值(如果设置了android:minWidth

    View源码中可以看到,关键在于getDefaultSize(int,int)方法它,返回最终传递给setMeasuredDimension(int,int)方法的数据,view就用这两个int值最为view的宽高了

    getDefaultSize(int,int)中关键在于MeasureSpec类

    MeasureSpec类封装了父ivew传递给子view的布局要求,每个MeasureSpc实例代表宽度和高度要求

    MeasureSpec.getMode(int)方法

    将根据传入的int测量值获取一个对应的模式:
    MeasureSpec.UNSPECIFIED:未指定.即父元素对子元素无任何限制
    MeasureSpec.EXACTLY:表示确定大小.即父元素决定子元素的确切大小
    MeasureSpec.AT_MOST:至多.即子元素最多能到达的大小
    (这三个模式与match_parent,wrap_parent的关系十分紧密,之后详细研究)

    MeasureSpec.getSize(int)
    根据传入的int测量值获取一个int值表达控件的大小

    MeasureSpec.makeMeasureSpec(int size,int mode)方法
    根据提供的大小值和模式,创建一个测量值


    android原实现中getDefaultSize(int,int)可以看到
    如果模式为MeasureSpec.UNSPECIFIED,最终大小就是我们申请的大小,如果模式为
    MeasureSpec.AT_MOST或者case MeasureSpec.EXACTLY,则最终结果则为MeasureSpec.getSize(measureSpec)的结果



    好了,到此android中view的默认实现过程基本结束,做一个简单总结:

    1.父类调用view.measure(int,int),传入测量值,建议值
    2.子view回调onMeasure(int,int),计算得到最终view的尺寸大小
    3.测量过程结束,进行layout过程

    所以,如果你自定义一个控件时,对其尺寸有特殊要求,则重写view的onMeasure(int.int)方法对尺寸进行定制即可
    (算完之后记得调用setMeasuredDimension(int,int)方法保存计算结果和设置标志,否则抛出异常)



    最后:
    以上都是个人理解,难免错漏,如需深入研究请转至大神博客:

    http://blog.csdn.net/qinjuning/article/details/8074262

     

    人生就是一局不能Again的DOTA
  • 相关阅读:
    初识DataGridView 表格数据控件
    数据适配:DataAdapter对象概述
    数据适配 DataAdapter对象
    DataSet常用简单方法
    数据集DataSet
    DateReader读取数据
    Command操作数据
    【bzoj3886】[Usaco2015 Jan]Moovie Mooving 状态压缩dp+二分
    【bzoj1572】[Usaco2009 Open]工作安排Job 贪心+堆
    【bzoj1593】[Usaco2008 Feb]Hotel 旅馆 线段树区间合并
  • 原文地址:https://www.cnblogs.com/hellenism/p/3673762.html
Copyright © 2011-2022 走看看