在這一篇中,我們主要是來實現FlowLayout中的onMeasure函數。
先說一說onMeasure,可以說重載 onMeasure(),onLayout(),onDraw()三個函數構建了自定義View的外觀形象。再加上onTouchEvent()等重載視圖的行爲,可以構建任何我們需要的可感知到的自定義View。我們知道,不管是自定義View還是系統提供的TextView這些,它們都必須放置在 LinearLayout等一些ViewGroup中,因此理論上我們可以很好的理解onMeasure(),onLayout(),onDraw()這 三個函數:1.View本身大小多少,這由onMeasure()決定;2.View在ViewGroup中的位置如何,這由onLayout()決 定;3.繪製View,onDraw()定義瞭如何繪製這個View。
在TextView中onMeasure是:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
...
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
}
else {
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
}
...
}
setMeasuredDimension(width, height);
}
首先我們要理解的是widthMeasureSpec, heightMeasureSpec這兩個參數是從哪裏來的?onMeasure()函數由包含這個View的具體的ViewGroup調用,因此值也是 從這個ViewGroup中傳入的。這裏我直接給出答案:子類View的這兩個參數,由ViewGroup中的 layout_width,layout_height和padding以及View自身的layout_margin共同決定。權值weight也是尤其需要考慮的因素,有它的存在情況可能會稍微複雜點。
瞭解了這兩個參數的來源,還要知道這兩個值的作用。我們只取 heightMeasureSpec作說明。這個值由高32位和低16位組成,高32位保存的值叫specMode,可以通過如代碼中所示的 MeasureSpec.getMode()獲取;低16位爲specSize,同樣可以由MeasureSpec.getSize()獲取。那麼 specMode和specSize的作用有是什麼呢?要想知道這一點,我們需要知道代碼中的最後一行,所有的View的onMeasure()的最後一行都會調用setMeasureDimension()函數的作用——這個函數調用中傳進去的值是View最終的視圖大小。也就是說 onMeasure()中之前所作的所有工作都是爲了最後這一句話服務的。
之後我們開始一片一片分析代碼:
第一部分:int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
分別獲得在widthMeasureSpec,heightMeasureSpec中的size與mode:
getSize,getMode和過程中涉及到的變量:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
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;
第二部分: int width = 0;
int height = 0;
// 記錄每一行的寬度與高度
int lineWidth = 0;
int lineHeight = 0;
// 得到內部元素的個數
int cCount = getChildCount();
在註釋中都已說明,就不解釋了;第三部分:
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
// 測量子View的寬和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到LayoutParams
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
// 子View佔據的寬度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
// 子View佔據的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin;
// 換行
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight())
{
// 對比得到最大的寬度
width = Math.max(width, lineWidth);
// 重置lineWidth
lineWidth = childWidth;
// 記錄行高
height += lineHeight;
lineHeight = childHeight;
} else
// 未換行
{
// 疊加行寬
lineWidth += childWidth;
// 得到當前行最大的高度
lineHeight = Math.max(lineHeight, childHeight);
}
// 最後一個控件
if (i == cCount - 1)
{
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
先獲得子View,再獲得子View的layoutParams,因爲是有間距的,所以注意要子View的LayoutParams強制轉化爲MarginLayoutParams;
重新計算子View的寬:因爲有左右間距,所以新的寬是原寬加上左右間距;同理,高也是如此;
之後判斷要不要換行,即已計算的行寬加上新的子View的寬,如果大於FlowLayout的寬減去左右邊距說明剩餘的寬度,不夠加上新的子View,要把新的子View放到新的一行中去(其實有做過acm的話,可以用到線段樹來進行優化),否則加入到原行中。之後在這兩種情況下對行高,行寬,高和寬做不同的調整;
如果到了最後一個子View的話:如果要換行的話,高只是記錄到之前的部分,而對換行後新加的高度即最後一個子View的高沒有加上,寬的話,如果最後一個子View的寬很大超過之前的話,也沒有更新;如果不要換行的話,最後一行的高與寬就始終沒有加上;所以最後一個View要進行特判;因爲無論是換行還是不換行,行寬都更新過了,所以只要和最大的寬做比較即可;高則只要加上行高即可;
第四部分: setMeasuredDimension(
//
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()+ getPaddingBottom()//
);
判斷是不是MeasureSpec.EXACTLY即是不是內容鋪滿父容器,是的話,就直接將一開始調用getSize得到的寬高傳入即可,如果不是,則要將測得的寬高加上邊距傳入到setMeasuredDimension函數中,來確定View最終的視圖大小。