Android源码分析之---View.MeasureSpec 解析

1、简介

    一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:

UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;

EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;

AT_MOST(至多),子元素至多达到指定大小的值。

     (ps:上段转自http://blog.csdn.net/kaixinbingju/article/details/8649218

MeasureSpecs的实现减少了对象的分配,这个类提供了 <size, mode>的pack和unpack进入这个int。


2、源码分析

MeasureSpec 源码

  public static class MeasureSpec {
        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;

        /**
         * Creates a measure specification based on the supplied size and mode.
         * 将尺寸和mode pack到 提供的 a measure specifiaction 中。
         * 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>
         *
         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
         * implementation was such that the order of arguments did not matter
         * and overflow in either value could impact the resulting MeasureSpec.
         * {@link android.widget.RelativeLayout} was affected by this bug.
         * Apps targeting API levels greater than 17 will get the fixed, more strict
         * behavior.</p>
         *
         * @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) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Extracts the mode from the supplied measure specification.
         * 获得 mode(前面提到三个模式之一)
         * @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);
        }

        static int adjust(int measureSpec, int delta) {
            return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }

3、Demo进行分析MeasureSpec中提供的方法。


这个Demo修改自提供APIdemo中的LabelView。

public class LabelView extends View {
    private Paint mTextPaint;
    private String mText;
    private int mAscent;
    
    /**
     * Constructor.  This version is only needed if you will be instantiating
     * the object manually (not from a layout XML file).
     * @param context
     */
    public LabelView(Context context) {
        super(context);
        initLabelView();
    }

    /**
     * Construct object, initializing with any attributes we understand from a
     * layout file. These attributes are defined in
     * SDK/assets/res/any/classes.xml.
     * 
     * @see android.view.View#View(android.content.Context, android.util.AttributeSet)
     */
    public LabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.LabelView);

        CharSequence s = a.getString(R.styleable.LabelView_text);
        if (s != null) {
            setText(s.toString());
        }

        // Retrieve the color(s) to be used for this view and apply them.
        // Note, if you only care about supporting a single color, that you
        // can instead call a.getColor() and pass that to setTextColor().
        setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000));

        int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0);
        if (textSize > 0) {
            setTextSize(textSize);
        }

        a.recycle();
    }

    private final void initLabelView() {
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        // Must manually scale the desired text size to match screen density
        mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
        mTextPaint.setColor(0xFF000000);
        setPadding(3, 3, 3, 3);
    }

    /**
     * Sets the text to display in this label
     * @param text The text to display. This will be drawn as one line.
     */
    public void setText(String text) {
        mText = text;
        requestLayout();
        invalidate();
    }

    /**
     * Sets the text size for this label
     * @param size Font size
     */
    public void setTextSize(int size) {
        // This text size has been pre-scaled by the getDimensionPixelOffset method
        mTextPaint.setTextSize(size);
        requestLayout();
        invalidate();
    }

    /**
     * Sets the text color for this label.
     * @param color ARGB value for the text
     */
    public void setTextColor(int color) {
        mTextPaint.setColor(color);
        invalidate();
    }
    @Override
    protected void onFinishInflate() {
    	// TODO Auto-generated method stub
    	super.onFinishInflate();
    	System.out.println("onFinishInflate");
    }

    /**
     * @see android.view.View#measure(int, int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	System.out.println("onMeasure "+widthMeasureSpec+"   "+heightMeasureSpec);
    	System.out.println("onMeasure "+Integer.toBinaryString(widthMeasureSpec)+"   "+Integer.toBinaryString(heightMeasureSpec));
       
    	//设置这个view的尺寸
    	setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

    /**
     * 
     * 测量决定这个View的width
     * @param measureSpec A measureSpec packed into an int
     * @return The width of the view, honoring constraints from measureSpec
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        // 通过这个 a measure specifiaction 值获取mode
        int specMode = MeasureSpec.getMode(measureSpec);
        
        // 通过这个 a measure specifiaction 值获取view的具体的尺寸
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text
            result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
                    + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }
        System.out.println("pack height:"+MeasureSpec.makeMeasureSpec(result, specMode));

        System.out.println("measureWidth "+result);	
        return result;
    }

    /**
     * 测量决定这个View的height
     * @param measureSpec A measureSpec packed into an int
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        mAscent = (int) mTextPaint.ascent();
        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text (beware: ascent is a negative number)
            result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
                    + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }
        System.out.println("measureHeight "+result);	

        return result;
    }

    /**
     * Render the text
     * 
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        System.out.println("onDraw");
        canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaint);
    }
}

我们可以看到通过MeasureSpec 中

 MeasureSpec.makeMeasureSpec(result, specMode)  pack了view的size和mode(这样的优势是 减少了对象的创建,提高了性能。)

在需要的size和mode的时候,通过

 int specMode = MeasureSpec.getMode(measureSpec);
 int specSize = MeasureSpec.getSize(measureSpec);

便可以获得对应的值。

运行时,产生的日志

《Android源码分析之---View.MeasureSpec 解析》


4 、MeasureSpec 原理分析

在这个Demo中,我们结合上面的计算的结果来分析,其如何对size进行pack和unpack计算的。

public class MeasureSpec {
	public static void main(String[] args) {
		int widthMeasureSpec=1073742256 ;
		
		   // 通过这个 a measure specifiaction 值获取mode
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        
        // 通过这个 a measure specifiaction 值获取view的具体的尺寸
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
		
        int   widthMeasureSpec2=  MeasureSpec.makeMeasureSpec(specSize, specMode);
        
        System.out.println(specMode);
        System.out.println(specSize);
        System.out.println("\n"+Integer.toBinaryString(specMode));
        System.out.println(Integer.toBinaryString(EXACTLY));
        
        System.out.println("\n"+widthMeasureSpec2);
        

		
	}
	
	 private static final int MODE_SHIFT = 30;
     private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

     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) {
             return size + mode;
         
     }

     public static int getMode(int measureSpec) {
    	 
         return (measureSpec & MODE_MASK);
     }

    
     public static int getSize(int measureSpec) {
         return (measureSpec & ~MODE_MASK);
     }

     static int adjust(int measureSpec, int delta) {
         return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
     }

    
     public static String toString(int measureSpec) {
         int mode = getMode(measureSpec);
         int size = getSize(measureSpec);

         StringBuilder sb = new StringBuilder("MeasureSpec: ");

         if (mode == UNSPECIFIED)
             sb.append("UNSPECIFIED ");
         else if (mode == EXACTLY)
             sb.append("EXACTLY ");
         else if (mode == AT_MOST)
             sb.append("AT_MOST ");
         else
             sb.append(mode).append(" ");

         sb.append(size);
         return sb.toString();
     }
}

此处的  移位、位与、或、异或、非。 可参考 Java 位运算(移位、位与、或、异或、非)

参考:

http://my.oschina.net/banxi/blog/51247

http://blog.csdn.net/kaixinbingju/article/details/8649218

    原文作者:dblackde
    原文地址: https://blog.csdn.net/dblackde/article/details/22738883
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞