计算视图大小(measure)的过程

来源:互联网 发布:手机淘宝淘口令怎么用 编辑:程序博客网 时间:2024/05/17 04:41

视图是无穷大的,layout_wdith和layout_height是指父视图分配给子视图的布局大小。

视图分两类,内容型视图和图形型视图。

内容型视图的布局大小一般由内容的多少决定。

图形型视图的布局大小一般由父视图给子视图分配的布局大小决定。

1、measure内部调用过程分析


上图中了描述了measure的调用过程,分析如下:

(1)过程开始于ViewRoot类中的performTraversals()

(2)performTraversals()方法中调用host.measure()

(3)首先host是一个View对象,在measure()方法中调用到onMeasure(),host一般是一个ViewGroup的实例,如LinearLayout就重载了onMeasure()方法

(4)在LinearLayout中的onMeasure()方法中,碾转调用到了父类ViewGroup的measureChildWithMargins()

(5)在ViewGroup的measureChildWithMargins()中又会调用到child.measure()方法

(6)如果child仍然是一个ViewGroup的实例,接着进行(3)~(5)步骤

(7)如果child是一个常规View对象,则调用View类的onMeasure()方法,通过setMeasuredDimension()设定该View对象测量出来的布局大小。

measure()方法中的参数是父视图给子视视图的规格,转换为32位二进制数,最高两位代表了specMode,后30位代表了specSize

specMode有三种

(a)MeasureSpec.EXACTLY确定的,最好是遵守父视图给你“确定”下来的布局大小。

(b)MeasureSpec.AT_MOST最多,最好不要超过父视图给你的布局大小。

(c)MeasureSpec.UNSPECIFIED没有限制,根据自己的需求确定你的视图的大小。

2、ViewGroup中的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的实例为LinearLayout对上面的代码分析如下:

(1)调用child.getLayoutParams(),getLayoutParams()方法是在View类中定义的,返回类型是ViewGroup.LayoutParams,对象名称是mLayoutParams

(2)MarginLayoutParams是ViewGroup.LayoutParams的子类,这里将ViewGroup.LayoutParams类型对象强转为了MarginLayoutParams类型对象,即是将父类转子类,这是为什么呢?只有一个解释就是child.getLayoutParams()返回的对象类型本身就是MarginLayoutParams的子类。

(3)那么getLayoutParams()返回的这个对象mLayoutParams,是在什么时候生成的呢?是在ViewGroup的addView()方法中。

    public void addView(View child, int index) {        LayoutParams params = child.getLayoutParams();        if (params == null) {            params = generateDefaultLayoutParams();            if (params == null) {                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");            }        }        addView(child, index, params);    }
刚添加View对象时,params是空的,是调用generateDefaultLayoutParams()生成的。一般ViewGroup的实例如LinearLayout是重载了generateDefaultLayoutParams()方法的。该方法返回的类型是LinearLayout.LayoutParams,该类正是MarginLayoutParams的子类。

(4)在addViewInner()方法中,通过child.mLayoutParams = params,把上一步generateDefaultLayoutParams()生成的对象赋值给了mLayoutParams
(5)综上,child.getLayoutParams()返回的对象是LinearLayout.LayoutParams类型,印证了(2)中的解释。

(6)两次调用getChildMeasureSpec(int spec, int padding, int childDimension)计算出子视图的宽高,从参数可以看出,子视图的大小是由父视图提供的规格和子视图期望得到的大小共同决定,当然还有padding

(7)接着调用child.measure()来征询子视图期望布局大小的意愿,子视图可以重载onMeasure()方法,并调用setMeasureDimension()函数来最终敲定布局大小。

 3、LinearLayout中的onMeasure()过程

View中的measure()方法不能重载,以保证View系统中的基本测量流程,ViewGroup的实例一般需要重载onMeasure()函数,来配合View的measure过程的完成。

在LinearLayout.onMeasure()方法中,首先判断是水平还是垂直,我们分析垂直 的情况

(1)final int count = getVirtualChildCount()计算出子视图个数

(2)调用两次MeasureSpec.getMode()计算出宽高模式

(3)用一个for循环计算出所有子视图的高度,用一个变量mTotalLength保存

(4)在for循环中调用了measureChildBeforeLayout()对每一个进行测量,该函数实际调用了ViewGroup中的measureChildWithMargins()函数。

(5)在步骤中暂时避过了lp.wight>0的子视图,后面父视图将剩余高度按照weight大小均匀分配给相应的子视图