Android View measure (二) 自定义UI控件measure相关

 

  本篇模拟三个角色:Android 架构师-小福、Android  控件开发工程师-小黑、 Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程。


小福负责分享

  •     measure的本质
  •     measure代码流程
  •     onMeasure方法与MeasureSpec
  •     提出问题

小黑负责分享

  •     布局控件开发中覆写Measure例子 - ok
  •     从遇到的一个异常说起
  •     什么时候需要覆写onMeaure? - ok
  •     view.getWidth与view.getMeasureWidth区别 - ok


Android  控件开发工程师-小黑的分享


一、布局控件开发中覆写Measure例子

    具体可查看之前写的例子《覆写onMeaure进行measure操作》,Android提供的布局FrameLaout、LinearLayout、RelativeLayout都是ViewGroup的子类,可以参考这些类的onMeausre实现,看看Android框架是如何处理的。

二、从遇到的一个异常说起

如果在onMeasure中使用return,并未进行setMeasuredDimension(width, height);类似操作,会出现以下异常
java.lang.IllegalStateException: onMeasure() did not set the measured dimension by calling setMeasuredDimension()

-待填充-

ViewGroup提供的measure方法
measureChildren() - 该函数内使用for()循环调用measureChild()对每一个子视图进行measure操作
measureChild() - 为每一个子视图进行measure操作
measureChildWidthMargins() - 该函数与measureChild的唯一区别在于,measure时考虑把margin及padding也作为子视图大小的一部分

三、什么时候需要覆写onMeaure?

    只要是自定义控件并且是ViewGroup都需要覆写onMeasure来指定其测量视图大小规则,但是Androd提供FrameLaout、LinearLayout、RelativeLayout等都是ViewGroup的子类并且都覆写了onMeasure方法,自定义布局的时候可以先考虑先从这些类的子类入手,可能更简单一些。

四、view.getWidth()与view.getMeasuredWidth()区别

    从名字上可以看出这两个方法都是为了获取宽度的,相应的也有获取高度的方法,但是问题在于两者的区别是什么?    下面直接从源码中查看这些值是从哪里来的,从而获知两者的区别。

    首先看下View.java中的getMeasuredWidth方法,源码如下:

public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
        AccessibilityEventSource {

    /**
     * Like {@link #getMeasuredWidthAndState()}, but only returns the
     * raw width component (that is the result is masked by
     * {@link #MEASURED_SIZE_MASK}).
     *
     * @return The raw measured width of this view.
     */
    public final int getMeasuredWidth() {
        // 直接返回mMeasuredWidth与后者相与清理掉其他开关获取真是measure大小
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
	
    /**
     * <p>This mehod must be called by {@link #onMeasure(int, int)} to store the
     * measured width and measured height. Failing to do so will trigger an
     * exception at measurement time.</p>
     *
     * @param measuredWidth The measured width of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     * @param measuredHeight The measured height of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     */
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        // 通常在onMeasure中调用,传入测量过的视图宽度与高度
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= MEASURED_DIMENSION_SET;
    }	


    /**
     * Bits of {@link #getMeasuredWidthAndState()} and
     * {@link #getMeasuredWidthAndState()} that provide the actual measured size.
     */
	 // MeasureSpec中的Mode或占用int类型中前几位
    public static final int MEASURED_SIZE_MASK = 0x00ffffff;

}

    从上面两个方法可以看出getMeasuredWidth的值是从mMeasuredWidth变量获取,而这个变量仅在View.setMeasuredDimension方法中继续初始化,从setMeasuredDimension方法的注释中就可以看出这个方式是在onMeasure中被调用,也就是getMeasuredWidth获取到的是在视图onMeasure方法中已经获取到视图的大小之后,才能进行赋值。getMeasuredWidth获取的是通过onMeasure测量后的值,在onMeasure执行之前可以调用但是获取到的都是0(int类型的默认初始化值)。

    上面已经知道getMeasuredWidth值的含义,接着来看下View.getWidth方法的源码:


public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
        AccessibilityEventSource {
		
    /**
     * Return the width of the your view.
     *
     * @return The width of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        // 视图的右侧减去左侧的值	
        return mRight - mLeft;
    }

    /**
     * Assign a size and position to this view.
     *
     * This is called from layout.
     *
     * @param left Left position, relative to parent
     * @param top Top position, relative to parent
     * @param right Right position, relative to parent
     * @param bottom Bottom position, relative to parent
     * @return true if the new size and position are different than the
     *         previous ones
     * {@hide}
     */
    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        ......
		
        // 四个值中任意一个发生改变就行		
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            ......

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;

            ......
        }
        return changed;
    }
	
    public void layout(int l, int t, int r, int b) {
        ......
        // 当前视图布局时执行,传入当前视图的上下左右边界值		
        boolean changed = setFrame(l, t, r, b);
		
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            ......
            // 上面执行完成后才会触发onLayout
            onLayout(changed, l, t, r, b);
			
            ......
        }
        ......
        mPrivateFlags &= ~FORCE_LAYOUT;
    }
}

    从上面的代码可以看出当视图layout操作时,会先调用setFrame方法传入left, top, right, bottom 这些值会在以后layout布局分析是进行详细解释,这些值是其父视图给他当前视图设定的可显示位置与大小(right - left 与 top - bottom获得)。 getWidth方法获取的宽度是当前视图可以在屏幕实际上占据的大小。

   简单总结下,getMeasuredWidth是视图onMeasure指定的宽度(可以笼统的理解为视图内容区域的大小,虽然不严谨但是系统提供的布局控件都是这样,仅在自定义视图中因为覆写onMeasure可以忽略layout_width,layout_heigh随意指定其宽高),而getWidth是视图父视图指定当前视图可以在屏幕上显示的区域。



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。