分析开源过程:https://github.com/ysyh55/android-verticalpager/
View绘制过程就好比你向银行贷款,
在执行onMeasure的时候,好比银行告诉你大概贷款额度有多少?你根据自己的需求,进行各方面的计算,计算出一个自己大概需要的金额,然后告诉询问需要多少贷款。贷款额度好比显示空间大小。即每一个子view告诉父类它要占用多大空间
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); pageHeight = getMeasuredHeight(); final int count = getChildCount(); for (int i = 0; i < count; i++) { //每个view空间都充满整个屏幕么,xml里设置的宽高失效 getChildAt(i).measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(pageHeight, MeasureSpec.UNSPECIFIED)); } //7.第一次初始化测量时 初始化当前屏幕 if (mFirstLayout) { scrollTo(getScrollYForPage(mCurrentPage), 0); mFirstLayout = false; } }onMeasure(int widthMeasureSpec, int heightMeasureSpec),其中widthMeasureSpec和heightMeasureSpec是银行告诉的大致额度。然后内部计算完成后,通过setMeasuredDimension(width, height)将实际需要的大小返回给父view。
即onMeasure用来确定确定view显示的大小(通过调用子view的measure,来确定子view的大小)
在执行onLayout的时候,好比银行收到你的贷款请求后,根据自身储备的情况及你的资质情况,批发了贷款的额度及领取的时间。领取的时间好比显示的起始位置,额度好比是显示空间的大小。即这里就是具体分配各自占用空间
在代码中就是设置其成员变量mLeft,mTop,mRight,mBottom的值,这几个值构成的矩形区域就是该View显示的位置,不过这里的具体位置都是相对与父视图的位置。
当我们继承ViewGroup时必须重载onLayout函数(ViewGroup中onLayout是abstract修饰),然而onMeasure并不要求必须重载,因为相对与layout来说,measure过程并不是必须的,
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { /**每个view之前的总高度**/ measuredHeight = 0; final int count = getChildCount(); int height; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != View.GONE) {//如果子view可见 if(i == 0) {//布第一个子view空间 child.getHeight(); child.layout(0, measuredHeight, right - left, measuredHeight + (int)(pageHeight*.96)); measuredHeight += (pageHeight*.96); } else {//布其他子view空间 height = pageHeight * (int)Math.ceil((double)child.getMeasuredHeight()/(double)pageHeight); height = Math.max(pageHeight, height); child.layout(0, measuredHeight, right - left, measuredHeight + height); measuredHeight += height; } } } }
看下View.java中函数layout和onLayout的源码:
public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~FORCE_LAYOUT; }函数layout的主体过程还是很容易理解的,首先通过调用setFrame函数来对4个成员变量(mLeft,mTop,mRight,mBottom)赋值,然后回调onLayout函数,最后回调所有注册过的listener的onLayoutChange函数。
对于View来说,onLayout只是一个空实现,一般情况下我们也不需要重载该函数:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }super.layout(l, t, r, b)调用的即是View.java中的layout函数,相比之下ViewGroup增加了LayoutTransition的处理,LayoutTransition是用于处理ViewGroup增加和删除子视图的动画效果,也就是说如果当前ViewGroup未添加LayoutTransition动画,或者LayoutTransition动画此刻并未运行,那么调用super.layout(l, t, r, b),继而调用到ViewGroup中的onLayout,否则将mLayoutSuppressed设置为true,等待动画完成时再调用requestLayout()。
上面super.layout(l, t, r, b)会调用到ViewGroup.java中onLayout,其源码实现如下:@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);和前面View.java中的onLayout实现相比,唯一的差别就是ViewGroup中多了关键字abstract的修饰,也就是说ViewGroup类只能用来被继承,无法实例化,并且其子类必须重载onLayout函数,而重载onLayout的目的就是安排其children在父视图的具体位置。
重载onLayout通常做法就是起一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。
那layout(l, t, r, b)中的4个参数l, t, r, b如何来确定呢?联想到之前的measure过程,measure过程的最终结果就是确定了每个视图的mMeasuredWidth和mMeasuredHeight,这两个参数可以简单理解为视图期望在屏幕上显示的宽和高,而这两个参数为layout过程提供了一个很重要的依据(但不是必须的),为了说明这个过程,我们来看下LinearLayout的layout过程:
void layoutVertical() { …… for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); …… setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
从setChildFrame可以看到LinearLayout中的子视图的右边界等于left + width,下边界等于top+height,也就是说在LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值。
layout过程必须要依靠measure计算出来的mMeasuredWidth和mMeasuredHeight来决定视图的显示大小吗?事实并非如此,layout过程中的4个参数l,
t, r, b完全可以由视图设计者任意指定,而最终视图的布局位置和大小完全由这4个参数决定,measure过程得到的mMeasuredWidth和mMeasuredHeight提供了视图大小的值,但我们完全可以不使用这两个值,可见measure过程并不是必须的。\\
说到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对函数之间的区别,getMeasuredWidth()、getMeasuredHeight()返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight
- mLeft和mBottom - mTop的值,看View.java中的源码便一清二楚了:
public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; } public final int getWidth() { return mRight - mLeft; }这也解释了为什么有些情况下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值。
总结:整个layout过程比较容易理解,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子视图的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子视图的具体位置。