126
技術社區[雲棲]
measureChildren的工作原理
無論是在重寫View還是ViewGroup的時候,尤其是ViewGrop的時候,往往不可避免的重寫onMeasure方法,我們一定會調用setMeasuredDimension()將測量好的寬高值傳遞進去。也不免調用measureChildren方法,來測量所有的子View的大小,下麵我們看看measureChildren方法是如何工作的。這對我們重寫onMeasure無疑是很有幫助的。因為一般我們都會看到這一行代碼
// 計算出所有的childView的寬和高 measureChildren(widthMeasureSpec, heightMeasureSpec);
但是它到底測量到什麼程度,滿足不滿足我們自定義ViewGroup對下麵一係列child尺寸的測量需求,不知道這個我們寫代碼就心裏沒底。所以我們有必要扒出它的老底來看看,由此來決定我們是否可以直接使用這個方法,還是由於我們有更多的效果要實現,有更多的因素需要考慮,這個方法不能滿足需求,需要自己寫方法來測量child。
同時我們在有必要重新寫方法來測量child的時候,我們也要從自帶方法的思路開始擴展。
說了一大堆,總之這個問題很重要。
下麵要了解它的工作原理,我們還是要來看看源碼:
(一)首先是measureChildren
/** * 遍曆所有的子view去測量自己(跳過GONE類型View) * @param widthMeasureSpec 從父容器傳遞給子容器的布局需求(寬) * @param heightMeasureSpec 從父容器傳遞給子容器的布局需求(高) */ protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
這部分很簡單,也就是遍曆所有的子View,如果View的狀態不是GONE就調用measureChild去進行下一步的測量。
(二)所以我們再來看一下measureChild
/** * 測量單個視圖,將寬高和padding加在一起後交給getChildMeasureSpec去獲得最終的測量值 * @param child 需要測量的子視圖 <pre name="code" > * @param widthMeasureSpec 從父容器傳遞給子容器的布局需求(寬) * @param heightMeasureSpec 從父容器傳遞給子容器的布局需求(高)*/ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { // 取得子視圖的布局參數 final LayoutParams lp = child.getLayoutParams(); // 通過getChildMeasureSpec獲取最終的寬高詳細測量值 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); // 將計算好的寬高詳細測量值傳入measure方法,完成最後的測量 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
這個方法就是對一個子視圖進行測量,其中一個重要的方法就是getChildMeasureSpec(),
(三)所以我們再來看一下getChildMeasureSpec
/** * * 結合父view的MeasureSpec與子view的LayoutParams信息去找到最好的結果 * (子view的確切大小由兩方麵共同決定:父view的MeasureSpec 子view的LayoutParams屬性) * * @param spec 父view的MeasureSpec * @param padding view當前尺寸的的內邊距和外邊距(padding,margin) * @param childDimension child在當前尺寸下的布局參數寬高值(LayoutParam.width,height) */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //父view的模式和大小 int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //通過父view計算出的子view = 父大小-邊距(父要求的大小,但子view不一定用這個值) int size = Math.max(0, specSize - padding); //子view想要的實際大小和模式(需要計算) int resultSize = 0; int resultMode = 0; //通過1.父view的MeasureSpec 2.子view的LayoutParams屬性這兩點來確定子view的大小 switch (specMode) { // 當父view的模式為EXACITY時,父view強加給子view確切的值 case MeasureSpec.EXACTLY: // 當子view的LayoutParams>0也就是有確切的值 if (childDimension >= 0) { //子view大小為子自身所賦的值,模式大小為EXACTLY resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; // 當子view的LayoutParams為MATCH_PARENT時(-1) } else if (childDimension == LayoutParams.MATCH_PARENT) { //子view大小為父view大小,模式為EXACTLY resultSize = size; resultMode = MeasureSpec.EXACTLY; // 當子view的LayoutParams為WRAP_CONTENT時(-2) } else if (childDimension == LayoutParams.WRAP_CONTENT) { //子view決定自己的大小,但最大不能超過父view,模式為AT_MOST resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 當父view的模式為AT_MOST時,父view強加給子view一個最大的值。 case MeasureSpec.AT_MOST: // 道理同上 if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 當父view的模式為UNSPECIFIED時,子view為想要的值 case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // 子view大小為子自身所賦的值 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 因為父view為UNSPECIFIED,所以MATCH_PARENT的話子類大小為0 resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 因為父view為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0 resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
總而言之,這些判斷和設置其實就是根據三種模式以及傳入的尺寸要求,還有需要考慮的padding和margin之後,比較全麵的計算出了一個測量值,了解了這些之後我們就可以確定什麼時候需要自己寫關於子視圖的測量部分,什麼時候我們隻需要簡單的一行代碼:
// 計算出所有的childView的寬和高 measureChildren(widthMeasureSpec, heightMeasureSpec);
就可以滿足我們的需求了,所以一切還是按需來處理。
在我個人看來,這個方法考慮的比我最初想象的要全麵多了,看來除了有比較特殊的需求,大部分的時候都是可以直接使用這個方法的。這還是省了不少事的。
如果您對我提到的模式或者是重寫過程不大了解的,具體的關於重寫onMeasure內容請詳見我的另外一篇博客:
https://blog.csdn.net/sunmc1204953974/article/details/38454267
希望大家能有所收獲,我也是學生,有什麼寫的不好的地方還請多多指教!
最後更新:2017-04-03 05:39:52