Linearlayout 測量過程原始碼解析(一)
轉自: https://github.com/razerdp/AndroidSourceAnalysis/blob/master/LinearLayout/android_widget_LinearLayout.md
LinearLayout 原始碼分析
宣告.本專案原始碼基於Api 23
1.談談LinearLayout
Android的常用佈局裡,LinearLayout屬於使用頻率很高的佈局。RelativeLayout也是,但相比於RelativeLayout每個子控制元件都需要給上ID以供另一個相關控制元件擺放位置來說,LinearLayout兩個方向上的排列規則在明顯垂直/水平排列情況下使用更加方便。
同時,出於效能上來說,一般而言功能越複雜的佈局,效能也是越低的(不考慮巢狀的情況下)。
相比於RelativeLayout無論如何都是兩次測量的情況下,LinearLayout只有子控制元件設定了weight屬性時,才會有二次測量,其餘情況都是一次。
另外,LinearLayout的高階用法除了weight,還有divider,baselineAligned等用法,雖然用的不常見就是了。
以下是LinearLayout相比於其他佈局所擁有的特性:
屬性 | 值型別 | 描述 | 備註 |
---|---|---|---|
orientation | int | 作為LinearLayout必須使用的屬性之一,支援縱向排布或者水平排布子控制元件 | |
weightSum | float | 指定權重總和 | 預設值為1.0 |
baselineAligned | boolean | 基線對齊 | |
baselineAlignedChildIndex | int | 該LinearLayout下的view以某個繼承TextView的View的基線對齊 | |
measureWithLargestChild | boolean | 當值為true,所有帶權重屬性的View都會使用最大View的最小尺寸 | |
divider(需要配合showDividers使用) | drawable in java/reference in xml | 如同您常在ListView使用一樣,為LinearLayout新增分割線 | [api>11] 同時如果是自己建立的drawable,請指定size |
【注意】divider附加屬性為showDividers(middle|end|beginning|none):
- middle 在每兩項之間新增分割線
- end 在整體的最後一項新增分割線
- beginning 在整體的首項新增分割線
- none 無
本篇主要針對LinearLayout垂直方向的測量、weight和divider進行分析,其餘屬性因為比較冷門,因此不會詳說
2.使用方法
對於LinearLayout的使用,相信您閉著眼睛都能寫出來,因此這裡就略過了。
3.原始碼分析
原始碼分析階段主要針對這幾個地方:
- measure流程
- weight的計算
後兩者的主要工作其實都是被包含在measure裡面的,因此對於LinearLayout來說,最重要的,依然是measure.
3.1 measure
在LinearLayout的onMeasure()
裡面,所有的測量都根據mOrientation這個int值來進行水平或者垂直的測量計算。
我們都知道,java中int在初始化不分配值的時候,都是預設的0,因此如果我們不指定orientation,measure則會按照水平方向來測量【水平orientation=0/垂直orientation=1】
接下來我們主要看看measureVertical方法,瞭解了垂直方向的測量之後,水平方向的也就不難理解了,為了篇幅,我們主要分析垂直方向的測量。
measureVertical方法除去註釋,大概200多行,因此我們分段分析。
方法主要分為三大塊:
- 一大堆變數
- 一個主要的for迴圈來不斷測量子控制元件
- 其餘引數影響以及根據是否有weight再次測量
3.1.1
一大堆變數
為何這裡要說說變數,因為這些變數都會極大的影響到後面的測量,同時也是十分容易混淆的,所以這裡需要貼一下。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { // mTotalLength作為LinearLayout成員變數,其主要目的是在測量的時候通過累加得到所有子控制元件的高度和(Vertical)或者寬度和(Horizontal) mTotalLength = 0; // maxWidth用來記錄所有子控制元件中控制元件寬度最大的值。 int maxWidth = 0; // 子控制元件的測量狀態,會在遍歷子控制元件測量的時候通過combineMeasuredStates來合併上一個子控制元件測量狀態與當前遍歷到的子控制元件的測量狀態,採取的是按位相或 int childState = 0; /** * 以下兩個最大寬度跟上面的maxWidth最大的區別在於matchWidthLocally這個引數 * 當matchWidthLocally為真,那麼以下兩個變數只會跟當前子控制元件的左右margin和相比較取大值 * 否則,則跟maxWidth的計算方法一樣 */ // 子控制元件中layout_weight<=0的View的最大寬度 int alternativeMaxWidth = 0; // 子控制元件中layout_weight>0的View的最大寬度 int weightedMaxWidth = 0; // 是否子控制元件全是match_parent的標誌位,用於判斷是否需要重新測量 boolean allFillParent = true; // 所有子控制元件的weight之和 float totalWeight = 0; // 如您所見,得到所有子控制元件的數量,準確的說,它得到的是所有同級子控制元件的數量 // 在官方的註釋中也有著對應的例子 // 比如TableRow,假如TableRow裡面有N個控制元件,而LinearLayout(TableLayout也是繼承LinearLayout哦)下有M個TableRow,那麼這裡返回的是M,而非M*N // 但實際上,官方似乎也只是直接返回getChildCount(),起這個方法名的原因估計是為了讓人更加的明白,畢竟如果是getChildCount()可能會讓人誤認為為什麼沒有返回所有(包括不同級)的子控制元件數量 final int count = getVirtualChildCount(); // 得到測量模式 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 當子控制元件為match_parent的時候,該值為ture,同時判定的還有上面所說的matchWidthLocally,這個變數決定了子控制元件的測量是父控制元件干預還是填充父控制元件(剩餘的空白位置)。 boolean matchWidth = false; boolean skippedMeasure = false; final int baselineChildIndex = mBaselineAlignedChildIndex; final boolean useLargestChild = mUseLargestChild; int largestChildHeight = Integer.MIN_VALUE; }
這裡有很多變數和值,事實上,直到現在,我依然沒有完全弄明白這些值的意義。
在這一大堆變數裡面,我們主要留意的是三個方面:
- mTotalLength:這個就是最終得到的整個LinearLayout的高度(子控制元件高度累加及自身padding)
- 三個跟width相關的變數
- weight相關的變數
3.1.2
測量
通過for迴圈不斷的得到子控制元件然後根據自己的定義進行賦值,這就是LinearLayout測量裡面最重要的一步。
這裡的程式碼比較長,去掉註釋後有100行左右,因此這裡採取重要地方註釋結合文字描述來分析。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { // ...接上面的一大堆變數 for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child == null) { // 目前而言,measureNullChild()方法返回的永遠是0,估計是設計者留下來以後或許有補充的。 mTotalLength += measureNullChild(i); continue; } if (child.getVisibility() == GONE) { // 同上,返回的都是0。 // 事實上這裡的意思應該是當前遍歷到的View為Gone的時候,就跳過這個View,下一句的continue關鍵字也正是這個意思。 // 忽略當前的View,這也就是為什麼Gone的控制元件不佔用佈局資源的原因。(畢竟根本沒有分配空間) i += getChildrenSkipCount(child, i); continue; } // 根據showDivider的值(before/middle/end)來決定遍歷到當前子控制元件時,高度是否需要加上divider的高度 // 比如showDivider為before,那麼只會在第0個子控制元件測量時加上divider高度,其餘情況下都不加 if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerWidth; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); // 得到每個子控制元件的LayoutParams後,累加權重和,後面用於跟weightSum相比較 totalWeight += lp.weight; // 我們都知道,測量模式有三種: // * UNSPECIFIED:父控制元件對子控制元件無約束 // * Exactly:父控制元件對子控制元件強約束,子控制元件永遠在父控制元件邊界內,越界則裁剪。如果要記憶的話,可以記憶為有對應的具體數值或者是Match_parent // * AT_Most:子控制元件為wrap_content的時候,測量值為AT_MOST。 // 下面的if/else分支都是跟weight相關 if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { // 這個if裡面需要滿足三個條件: // * LinearLayout的高度為match_parent(或者有具體值) // * 子控制元件的高度為0 // * 子控制元件的weight>0 // 這其實就是我們通常情況下用weight時的寫法 // 測量到這裡的時候,會給個標誌位,稍後再處理。此時會計算總高度 final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { // 到這個分支,則需要對不同的情況進行測量 int oldHeight = Integer.MIN_VALUE; if (lp.height == 0 && lp.weight > 0) { // 滿足這兩個條件,意味著父類即LinearLayout是wrap_content,或者mode為UNSPECIFIED // 那麼此時將當前子控制元件的高度置為wrap_content // 為何需要這麼做,主要是因為當父類為wrap_content時,其大小實際上由子控制元件控制 // 我們都知道,自定義控制元件的時候,通常我們會指定測量模式為wrap_content時的預設大小 // 這裡強制給定為wrap_content為的就是防止子控制元件高度為0. oldHeight = 0; lp.height = LayoutParams.WRAP_CONTENT; } /**【1】*/ // 下面這句雖然最終呼叫的是ViewGroup通用的同名方法,但傳入的height值是跟平時不一樣的 // 這裡可以看到,傳入的height是跟weight有關,關於這裡,稍後的文字描述會著重闡述 measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); // 重置子控制元件高度,然後進行精確賦值 if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; // getNextLocationOffset返回的永遠是0,因此這裡實際上是比較child測量前後的總高度,取大值。 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } } if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) { mBaselineChildTop = mTotalLength; } if (i < baselineChildIndex && lp.weight > 0) { throw new RuntimeException("A child of LinearLayout with index " + "less than mBaselineAlignedChildIndex has weight > 0, which " + "won't work. Either remove the weight, or don't set " + "mBaselineAlignedChildIndex."); } boolean matchWidthLocally = false; // 還記得我們變數裡又說到過matchWidthLocally這個東東嗎 // 當父類(LinearLayout)不是match_parent或者精確值的時候,但子控制元件卻是一個match_parent // 那麼matchWidthLocally和matchWidth置為true // 意味著這個控制元件將會佔據父類(水平方向)的所有空間 if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) { matchWidth = true; matchWidthLocally = true; } final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; maxWidth = Math.max(maxWidth, measuredWidth); childState = combineMeasuredStates(childState, child.getMeasuredState()); allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; if (lp.weight > 0) { weightedMaxWidth = Math.max(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); } i += getChildrenSkipCount(child, i); } }
在程式碼中我註釋了一部分,其中最值得注意的是measureChildBeforeLayout()
方法。這個方法將會決定子控制元件可用的剩餘分配空間。
measureChildBeforeLayout()
最終呼叫的實際上是ViewGroup的measureChildWithMargins()
,不同的是,在傳入高度值的時候(垂直測量情況下),會對weight進行一下判定
假如當前子控制元件的weight加起來還是為0,則說明在當前子控制元件之前還沒有遇到有weight的子控制元件,那麼LinearLayout將會進行正常的測量,若之前遇到過有weight的子控制元件,那麼LinearLayout傳入0。
那麼measureChildWithMargins()
的最後一個引數,也就是LinearLayout在這裡傳入的這個高度值是用來幹嘛的呢?
如果我們追溯下去,就會發現,這個函式最終其實是為了結合父類的MeasureSpec以及child自身的LayoutParams來對子控制元件測量。而最後傳入的值,在子控制元件測量的時候被新增進去。
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); }
在官方的註釋中,我們可以看到這麼一句:
- @param heightUsed Extra space that has been used up by the parent vertically (possibly by other children of the parent)
事實上,我們在程式碼中也可以很清晰的看到,在getChildMeasureSpec()
中,子控制元件需要把父控制元件的padding,自身的margin以及一個可調節的量三者一起測量出自身的大小。
那麼假如在測量某個子控制元件之前,weight一直都是0,那麼該控制元件在測量時,需要考慮在本控制元件之前的總高度,來根據剩餘控制元件分配自身大小。而如果有weight,那麼就不考慮已經被佔用的控制元件,因為有了weight,子控制元件的高度將會在後面重新賦值。
3.2 weight
3.2.1
weight的再次測量
在上面的程式碼中,LinearLayout做了針對沒有weight的工作,在這裡主要是確定自身的大小,然後再針對weight進行第二次測量來確定子控制元件的大小。
我們接著看下面的程式碼:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { //...接上面 // 下面的這一段程式碼主要是為useLargestChild屬性服務的,不在本文主要分析範圍,略過 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerHeight; } if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child == null) { mTotalLength += measureNullChild(i); continue; } if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); // Account for negative margins final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } } // Add in our padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Reconcile our calculated size with the heightMeasureSpec int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; }
上面這裡是為weight情況做的預處理。
我們略過useLargestChild 的情況,主要看看if處理外的程式碼。在這裡,我沒有去掉官方的註釋,而是保留了下來。
從中我們不難看出heightSize做了兩次賦值,為何需要做兩次賦值。
因為我們的佈局除了子控制元件,還有自己本身的background,因此這裡需要比較當前的子控制元件的總高度和背景的高度取大值。
接下來就是判定大小,我們都知道測量的MeasureSpec實際上是一個32位的int,高兩位是測量模式,剩下的就是大小,因此heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
作用就是用來得到大小的精確值(不含測量模式)
接下來我們看這個方法裡面第二佔比最大的程式碼:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { //...接上面 //算出剩餘空間,假如之前是skipp的話,那麼幾乎可以肯定是有剩餘空間(同時有weight)的 int delta = heightSize - mTotalLength; if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { // 限定weight總和範圍,假如我們給過weighSum範圍,那麼子控制元件的weight總和受此影響 float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child.getVisibility() == View.GONE) { continue; } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { // 全篇最精華的一個地方。。。。擁有weight的時候計算方式,ps:執行到這裡時,child依然還沒進行自身的measure // 公式 = 剩餘高度*(子控制元件的weight/weightSum),也就是子控制元件的weight佔比*剩餘高度 int share = (int) (childExtra * delta / weightSum); // weightSum計餘 weightSum -= childExtra; // 剩餘高度 delta -= share; final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width); if ((lp.height !=相關推薦
Linearlayout 測量過程原始碼解析(一)
轉自: https://github.com/razerdp/AndroidSourceAnalysis/blob/master/LinearLayout/android_widget_LinearLayout.md LinearLayout 原始碼分析 宣告.本專案
View測量過程原始碼解析
量算過程概述 如果要進行量算的View是ViewGroup型別,那麼ViewGroup會在onMeasure方法內會遍歷子View依次進行量算,本文重點說明非ViewGroup的View的量算過程,因為我們一旦瞭解了非ViewGroup的View的量算過程,ViewGro
原始碼解析一次OKHttp請求的過程
OkHttp這個庫有多優秀作為Androider大家心裡都明白,應該說合格的開發者都認識它。那麼,這裡簡單看個OKHttp的介面請求姿勢: OkHttpClient okHttpClient = new OkHttpClient(); Request reque
Spring原始碼解析 - 一、環境搭建
環境搭建 1. 安裝git: https://desktop.github.com/ 安裝git以及使用教程: https://blog.csdn.net/qq_39705793/article/deta
Promise原始碼解析(一) 詞法作用域的那些事
Promise很複雜…恩…原來覺得不就是詞法作用域嗎, 原來覺得詞法作用域不就是呼叫時的堆疊是定義時的嘛 結果…一個簡單概念被玩成了這樣…promise.js原始碼從https://blog.csdn.net/qq_22844483/article/details/73655738所得 謝謝原作者(未申請轉載
React-Redux 原始碼解析 一(createStore)
createStore 一般而言,我檢視一個庫的原始碼,首先回檢視對應方法的引數,其次是對應的return ,然後再看程式碼的具體實現。 通過檢視原始碼,發現createStore 方法返回了一個物件, 該物件共暴露出了五個方法,四個常用的方法: return {
Elastic-Job原始碼解析(一)之與Spring完美整合
看過小編寫SpringFramework原始碼解析的同學應該對Spring支援自定義標籤還有點印象吧,沒有的話我們回顧下,然後看看Elastic-Job是如何巧妙的利用自定義標籤生成Job任務的吧。請注意這裡用了一個巧妙關鍵字。我們看它如何巧妙的吧。 Spring自定義
Spring Cloud Config原始碼解析(一)
這是第一次寫原始碼的解析,主要是給自己留一個印象,有很多不充足的地方以後自己慢慢補充。這個spring cloud config原始碼的解析是以2.1.0.BUILD-SNAPSHOT為基礎,時間是2018-10-05以前的程式碼來分析。spring cloud config主要有3
Spring bean 建立過程原始碼解析
相關文章 Spring 中 bean 註冊的原始碼解析 前言 在上一篇檔案 Spring 中 bean 註冊的原始碼解析 中分析了 Spring 中 bean 的註冊過程,就是把配置檔案中配置的 bean 的資訊載入到記憶體中,以 BeanDefinition 物件的形
Django-Filter原始碼解析一
Django Filter原始碼解析最近在看Django-FIlter專案的原始碼,學習一下別人的開發思想;整體介紹首先,我從其中一個測試用例作為入口,開始了debug之路,一點一點的斷點,分析它的執行順序,如圖:ok,下面從程式碼的層面進行分析:urlurl(r'^books/$', FilterView.
Spring IOC容器啟動流程原始碼解析(一)——容器概念詳解及原始碼初探
目錄 1. 前言 1.1 IOC容器到底是什麼 IOC和AOP是Spring框架的核心功能,而IOC又是AOP實現的基礎,因而可以說IOC是整個Spring框架的基石。那麼什麼是IOC?IOC即控制反轉,通俗的說就是讓Spring框架來幫助我們完成物件的依賴管理和生命週期控制等等工作。從面向物件的角度來說,
JDK8-HashMap原始碼解析(一)
注:HashMap的建構函式較多,就不一一解讀了,這裡以無參建構函式為例 /** * Constructs an empty <tt>HashMap</tt> with the default initial capa
Mobx 原始碼解析 一(observable)
前言 在Git 找到Mobx 的原始碼, 發現其是使用TypeScript 編寫,因為我對Typescrit 沒有專案經驗,所以我先會將其編譯成JavaScript,所以我們可以執行如下指令碼或者從CDN直接下載一份編譯過的原始碼,我們可以選擇umd 規範指令碼: git
Vue原始碼解析(一)
前言:接觸vue已經有一段時間了,前面也寫了幾篇關於vue全家桶的內容,感興趣的小夥伴可以去看看,剛接觸的時候就想去膜拜一下原始碼~可每次鼓起勇氣去看vue原始碼的時候,當看到幾萬行程式碼的時候就直接望而卻步了,小夥伴是不是也跟我當初一樣呢? 下面分享一下我看vue原始碼的一些感觸,然後
jdk1.8原始碼解析一:Object類
Object 類屬於 java.lang 包,此包下的所有類在使用時無需手動匯入,系統會在程式編譯期間自動匯入。Object 類是所有類的基類,當一個類沒有直接繼承某個類時,預設繼承Object類,也就是說任何類都直接或間接繼承此類,Object 類中能訪問的方法在所有類
iOS總結-網路框架-AFNetworking原始碼解析(一)
AF分為5個功能模組: 網路通訊模組(AFHTTPSessionManager/AFURLSessionManager) 網路狀態監聽模組(AFURLResponseSerialization) 網路通訊安全策略模組(AFSecurityPoli
基於django 開發的框架 jumpserver 原始碼解析(一)
基於類的檢視,以及url 路由解析。 jumpserver 這個輪子是好跑的輪子,又大又圓,對原始碼進行解析。 jumpserver 中 用了 大量的基於類的檢視函式,相對抽象一點,隱藏了一些細節。大量使用 類似下面的url路由。 urlpat
死磕Netty原始碼之ChannelPipeline原始碼解析(一)
前言 ChannelPipeline資料管道是ChannelHandler資料處理器的容器,負責ChannelHandler的管理和事件的攔截與排程 原始碼分析 初始化 在Channel初始化時會呼叫到AbstractChannel的構造方法
ReactiveSwift原始碼解析(一) Event與Observer程式碼實現
ReactiveCocoa這個框架是做什麼用的本篇部落格就不做過多贅述了,什麼是“響應式程式設計”也不多聊了,自行Google吧。本篇部落格的主題是解析ReactiveCocoa框架中的核心模組ReactiveSwift中的兩個核心類的實現,也就是對Event和Observer這兩個類進行解析。之所以把這兩個
【Spring原始碼解析(一)】Idea搭建Spring原始碼閱讀環境
準備階段 Idea Gradle Git jdk 詳細步驟 以上工具安裝好,這裡主要記錄一下Git和Gradle,工作一直都是用的svn和Maven,所以對於Git和Gradle瞭解的不算多。 (1) Gradle安裝 Gradle的安裝很簡單,和