目錄
一、開場白
二、View的繪制流程
2.1測量的程序
2.2布局的程序
2.3繪制的程序
一、開場白
開講之前我們先預設一種自定義ViewGroup的場景:我們知道LinearLayout、FrameLayout、RelativeLayout...都是系統定義實作的布局,我們想要自定義一個FlowLayout流式布局實作我們自己要的效果(自定義不就是按照自己想要的效果實作的一種布局),流式布局目前Google官方還沒幫我們提供,但網上有很多自定義的流式布局,也都是開發者們自己繼承ViewGroup實作的,今天以自定義ViewGroup實作流式布局FlowLayout為大前提講解View的繪制程序,

二、View的繪制流程
牢記我們開場白的大前提:FlowLayout,就是個自定義ViewGroup,帶著這個思路去看下面的程序,

View的生命周期圖:

View的繪制流程:規規矩矩的測量measure()---->整整齊齊的布局layout()---->漂漂亮亮的裝修draw();
類比一個生活中的場景:別人送你一塊地皮(ViewGroup)做房子,我也不知道這塊地多大,并且你手上有幾件裝修好了的房間(子View),施工師傅先用尺子測量你要用這塊地要隔開幾個房間都給你先量好你手上每個房間的大小,記住這個時候還沒有測量這整塊地的大小,我也不需要去測,因為我只要把每個房間的大小測量好了,拼接起來(各個房間拼在一起形成行行列列,這個時候腦海中就要有流式布局的模型了:取各行中最長的那一行作為房子的寬,取所有行加起來的高度作為房子的長),這樣就測量出了這塊地的長寬了,這就是measure()的程序,接下來就是布局了,你剛剛只是把每個單獨的房間寬高測出來了,但是你怎么把每個房間擺放在這塊地皮的哪個位置就是layout()的作業了,回圈呼叫每個子View的layout()方法,把坐標引數穿進去就把子View都布局好了,裝修draw其實這里可以不需要,因為你手上的每個房間里里外外都裝修好了,經過布局有規則的拼接之后就是一棟漂亮的房子了,完工,
上面的地皮就是FlowLayout,每個房間就類比TextView(每個TextView系統都幫我們實作裝修好了,內部實作了onDraw()方法,就是可以直接使用的),我們要做的就是自定義FlowLayout的時候測量出自己的大小和layout布局好每個子View就行,
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:background="@drawable/shape_textview"
android:text="搜索歷史"
android:textColor="@color/colorPrimary" />
<com.example.flowlayoutdamo.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingLeft="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_textview"
android:text="帥哥美女"
android:textColor="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_textview"
android:text="帥哥111美女"
android:textColor="@color/colorAccent" />
......
</com.example.flowlayoutdamo.FlowLayout>
</LinearLayout>
2.1測量的程序
measure測量的程序就是確定View的大小的程序,
首先捋順幾個概念:onMeasure()、measure()、setMeasuredDimension(),先看張View層級結構圖:

1、measure()為final方法,不可重寫;onMeasure()可重寫,
View.java原始碼:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
}
2、父類重寫的onMeasure方法中呼叫子View的measure(),子View的measure()呼叫子View的onMeasure(),如此依次遞回呼叫,
//FrameLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
...
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
//View
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
} else {
...
}
...
}
下面用一個實體來講一下這個測量的遞回呼叫的程序:xml布局就用上面FlowLayout所在的布局(在上面那段有“帥哥美女”的代碼中)
------> 布局的根布局是LinearLayout,LinearLayout重寫了onMeasure()方法,在這個方法中會去遍歷根布局LinearLayout所有的子View,并呼叫每一個子View(這里有兩個子View:TextView和FlowLayout)的measure()方法,在這兩個子View的measure()中又會呼叫自身重寫的onMeasure()方法(呼叫TextView的onMeasure()和FlowLayout的onMeasure()方法),而FlowLayout又是一個ViewGroup,里面又有很多子View,在呼叫FlowLayout的onMeasure中會遍歷呼叫它里面每個子View的measure()方法,每個子View又會呼叫自身重寫的onMeasure()方法,到這里你以為就結束了?在這個程序中,每個View是上一層布局的子View又是下一層布局的父View,每個View在onMeasure()方法中都會呼叫最終的setMeasuredDimension()方法來保存自己的測量結果,而且你會發現既然是遞回呼叫,為什么每個onMeasure方法并沒有把測量結果回傳給上一層呼叫它的View,以此測量結果作為上一層View測量大小的參考因素?這是因為每個View呼叫onMeasure()會進一步呼叫setMeasuredDimension()來保存這個測量結果,你父View想拿到子View的測量結果可以通過getMeasureWidth()和getMeasureHeight()兩個方法獲得,不把這個測量結果作為onMeasure()方法的回傳值是因為父View的測量結果不全是取決于子View的測量結果,也跟父View的父View傳給他的參考大小有關系,所以決定一個View的大小不僅和它的子View有關系,還和它的父View有關系,還和自身在xml設定的屬性值有關系,<------

measure() 方法被父View呼叫,在 measure() 中做一些準備和優化作業后,呼叫 onMeasure() 來進行實際的自我測量,onMeasure() 做的事,View和ViewGroup不一樣:
-
View:View在
onMeasure()中會計算出自己的尺寸然后保存 -
ViewGroup:ViewGroup在
onMeasure()中會呼叫所有子View的measure()讓它們進行自我測量,并根據子View計算出期望尺寸來計算出它們的實際尺寸和位置(實際上99%的父View都會使用子View繪制出的期望尺寸來作為實際尺寸)然后保存,同時,它也會根據子View的尺寸和位置來計算出自己的尺寸然后保存
接下來就詳細介紹這三種關系:以自定義FlowLayout為例

1、上面FlowLayout這段代碼中,先度量孩子大小再度量自己大小,就說明子View的測量大小是決定父View大小的一個因素,那如何測量子View的大小呢?
測量子View的大小不就是去決議xml中FlowLayout包裹下的各個子View的屬性值layout_width和layout_height,把這兩個屬性的值如果不是具體的大小值變成具體的大小值(因為有三種情況wrap_content、match_parent、100dp),不就度量出來每個子View的大小了,
2、那么如何把xml中的三種表現形式變成具體的dp值呢?
我們都知道xml的屬性值layout_width和layout_height的java代碼表現形式是LayoutParams,都在這個類里面,LayoutParams又是ViewGroup的靜態內部類:

這里還不能明確的將這些wrap_content和match_parent轉成具體的值,因為FlowLayout的子View必須經過在FlowLayout的onMeasure中呼叫每個子View的measure()才算測量了子View的大小,關鍵又在于如何呼叫子View的measure()方法呢?
3、現在知道了呼叫子View的measure(),接下來關鍵在于子View的measure呼叫傳的引數怎么來:![]()
這個時候我們要接觸一個新的概念了:MeasureSpec類
你會發現這個單詞在自定義View中看見的頻率是最高的,在我們的FlowLayout的onMeasure(int widthMeasureSpec, int heightMeasureSpec)就有這兩個引數,這兩個引數又是從哪里來的呢?其實我們從上面的邏輯中知道,FlowLayout是LinearLayout的子View,在呼叫LinearLayout的onMeasure()的時候會呼叫子View(FlowLayout)的measure(int widthMeasureSpec, int heightMeasureSpec),并會傳入兩個引數,這個measure(int widthMeasureSpec, int heightMeasureSpec)又會呼叫到FlowLayout的onMeasure(),并把這兩個引數傳入onMeasure(),所以FlowLayout的onMeasure這兩個引數就是這么來的,
4、那么這個MeasureSpec到底是什么?
MeasureSpec是View的內部類,它封裝了一個View的尺寸,在onMeasure()當中會根據這個MeasureSpec的值來確定View的寬高,MeasureSpec的值保存在一個int值當中,一個int值有32位,高兩位表示模式mode低30位表示大小size,即MeasureSpec = mode + size,在MeasureSpec當中一共存在三種mode:UNSPECIFIED(00)、EXACTLY(01) 和AT_MOST(10),對于View來說,MeasureSpec的mode和Size有如下意義
UNSPECIFIED:不對View大小做限制,一般系統使用
EXACTLY:精準模式:確切的大小,如:100dp
AT_MOST:最大模式:大小不可超過某數值,如:matchParent, 最大不能超過你父View
使用方式:
// 獲取測量模式(Mode)
int specMode = MeasureSpec.getMode(measureSpec)
// 獲取測量大小(Size)
int specSize = MeasureSpec.getSize(measureSpec)
// 通過Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
所以每個View都有三種模式中的其中一種,然后后面跟一個size大小,那么問題來了,如何確定是哪種模式+size呢?
5、如何確定View的MeasureSpec(mode+size)呢?
這個問題就回歸到如何將xml中的屬性layout_width和layout_height的值轉成MeasureSpec?這時候又回歸到如何將LayoutParams轉換成MeasureSpec?這時候就涉及到一種演算法了:原始碼
/**
*引數1:spec是父View的MeasureSpec
*引數2:padding是父View預留的邊界值
*引數3:子View在xml檔案中設定的值:100dp、wrap_content、match_parent
*
* 這里我們把FlowLayout當父View,FlowLayout中的每個TextView當子View來理解下面代碼
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//當父View為一個精確值時,為子View賦值
case MeasureSpec.EXACTLY:
//如果子view有自己的尺寸,則使用自己的尺寸
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//當子View是match_parent,將父View的大小賦值給子View,只是給了子View一個參考值,就是要告訴子View,你別超過我父View的大小,最大只能和我一樣大,而并不是實際的大小值,實際具體的值要等走了子View的measure()才能知道
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//如果子View是wrap_content,設定子View的最大尺寸為父View,同上
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父布局給子View了一個最大界限
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//如果子view有自己的尺寸,則使用自己的尺寸
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
// 父View的尺寸為子View的最大尺寸
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//父View的尺寸為子View的最大尺寸
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父布局對子View沒有做任何限制
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
//如果子view有自己的尺寸,則使用自己的尺寸
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//因父布局沒有對子View做出限制,當子View為MATCH_PARENT時則大小為0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//因父布局沒有對子View做出限制,當子View為WRAP_CONTENT時則大小為0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
注意:我們可以注意到這個演算法的三個引數:spec來自父View,padding來自自己、childDimension來自子View,你就會明白測量View大小的時候關系到父View、自身屬性padding、子View三個因素才能決定,
如何獲取到childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);這兩個引數的值全在上面這個演算法中,從原始碼可以看出來,子View的測量模式是由子View的LayoutParam和父View的MeasureSpec來決定的,于是有了下面的經典圖:每個遞回的View都是這么一個創建規則,

6、那么回歸到上面最初的問題,如何度量子View,就是這樣:
//將layoutParams通過演算法轉變為measureSpec
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);//childLP.width可能是100dp、wrap_content、march_parent
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingBottom + paddingTop, childLP.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
7、上面的6個步驟都是在解決關鍵點1:如何度量子View的大小?接下來是關鍵點2:如何度量自己并保存?setMeasuredDimension(width,height);
每個view在onMeasure()方法中都會呼叫setMeasuredDimension()來度量自己的最終大小,那么問題又來了,這方法的兩個引數如何獲得?
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//獲取子view的度量寬高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
在上面我們已經給子View進行了measure(),那么就可以通過getMeasureWidth()和getMeasureHeight()來獲取測量的寬高了,接下來就是自己寫測量自身FlowLayout的演算法了:高度就是所有行的高度和、寬度就是各個行中最長的那一行總的長度,可以先不理會下面代碼:
//獲取子view的度量寬高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
if (childMeasuredWidth + lineWidthUsed > selfWidth) {
allLines.add(lineViews);
lineHeights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
//view 是分行layout的,所以要記錄每一行有哪些view,這樣可以方便layout布局
lineViews.add(childView);
//每一行都有自己的寬高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasuredHeight);
這樣我們就計算出來了所有行的高度和parentNeededHeight、寬度就是各個行中最長的那一行總的長度parentNeededWidth,可能就會有人覺得那就可以度量FlowLayout大小了setMeasuredDimension(parentNeededWidth, parentNeededHeight); 這里不要忘了FlowLayout也有自己的mode,也要考慮進去:如果父View給的mode是EXACTLY,是不需要考慮上面算了這么久的子View的演算法程序,因為有個這樣的關系:selfWidth > parentNeededWidth,想想是不是有個這個關系存在,
//度量自己
//flowlayout作為viewGroup,他自己也是一個view,他的大小也需要根據他的父親給他提供的MeasureSpec來度量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
setMeasuredDimension(realWidth, realHeight);
這樣一個完整的測量程序就結束了,
2.2布局的程序
view.layout(left, top, right, bottom);
layout() 方法被父View呼叫,在 layout() 中它會保存父View傳進來的自己的位置和尺寸,并且呼叫 onLayout() 來進行實際的內部布局,onLayout() 做的事,View和ViewGroup也不一樣:
-
View:由于沒有子View,所以View的
onLayout()什么也不做 -
ViewGroup:ViewGroup在
onLayout()中會呼叫自己的所有子View的layout()方法,把它們的尺寸和位置傳給它們,讓它們完成自我的內部布局

也是輪訓所有的子View,分別呼叫每個子View的layout()方法,并傳入相關的上下左右引數,而這些引數就要參考布局坐標系了:布局坐標系的每個View的原點是父View的左上角為原點的,

拿到每個view距離它的父View的left, top, right, bottom,這里又是一套距離演算法了:
每個view的left:一行中左邊所有view的寬,加上間距就是當前view的left引數了;
每個view的top:當前行坐在行上面所有行的高度+間距就是當前行的top引數;
每個view的right:當前行的left+當前view的寬度,就是當前view的right引數;
每個view的bottom:當前行的top+當前view的高度,就是當前view的bottom引數,
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int curL = getPaddingLeft();
int curT = getPaddingTop();
for (int i = 0; i < allLines.size(); i++) {
List<View> lineViews = allLines.get(i);
int lineHeight = lineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View view = lineViews.get(j);
int left = curL;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom);
curL = right + mHorizontalSpacing;
}
curT = curT + lineHeight + mVerticalSpacing;
curL = getPaddingLeft();
}
}
這樣一個完整的布局程序就結束了,關鍵還在于上面的測量程序,
2.3繪制的程序
前面測量中舉建房子的例子,從中就知道FlowLayout根本不需要draw,它本身就是一塊空白透明區域,最終看到花花綠綠的流式布局的效果其實都是各個子View重寫了onDraw,每個單獨的個體負責自身的裝飾,最后拼裝在一起就是一棟漂亮的房子,但是你說我就是要重寫FlowLayout的onDraw()可不可以,那當然是可以,可以去看看LinearLayout原始碼:就重寫了onDraw()用來畫分割線的

這里自定義ViewGroup重寫onDraw()方法有個坑存在的:你會發現你寫了復寫了這個方法不一定會被呼叫,那么問題來了:什么時候會被呼叫呢?onDraw不執行的原因?

這是ViewGroup的原始碼,你會看到有個標志位變數:WILL_NOT_DRAW,字面意思不就是將不允許繪制,很明顯就是這個變數在控制這onDraw的呼叫,為true就是不呼叫,false就是呼叫,那么我們要找找哪些地方使用了這個和哪些地方會去改變這個變數的值:
View.java setFlags方法
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null
|| mDefaultFocusHighlight != null
|| (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);
}
上面原始碼第二個判斷和第三個判斷的作用:
1、如果設定了WILL_NOT_DRAW標記為true,那么繼續檢查background、foreground(mDrawable欄位)、focusHighLight是否有值,如果三者任意一個設定了,那么將PFLAG_SKIP_DRAW標記清除,否則將該標記加上,
2、如果沒有設定WILL_NOT_DRAW標記為false,那么將PFLAG_SKIP_DRAW標記清除,
至此,我們知道了假如FlowLayout的 onDraw()方法沒有執行的原因:viewGroup默認設定了WILL_NOT_DRAW標記,進而設定了PFLAG_SKIP_DRAW標記,而在繪制的時候通過判斷PFLAG_SKIP_DRAW標記來決定是否呼叫FlowLayout的draw(x)方法,最終呼叫onDraw()方法,而view默認沒有設定WILL_NOT_DRAW標記,也就沒有后面的事了,
結論:若要ViewGroup onDraw()執行,只需要setWillNotDraw(false)、設定背景、設定前景、設定焦點高亮,4個選項其中一項滿足即可,
完整代碼如下:僅僅是個了解view的繪制程序的demo,使用的程序還有些問題需要考慮進去:gravity、margin、等等
public class FlowLayout extends ViewGroup {
private static final String TAG = "FlowLayout";
private int mHorizontalSpacing = dp2px(16);//每個item的橫向間距
private int mVerticalSpacing = dp2px(16);//每個item的縱向間距
//記錄所有的行,一行一行的存盤,用于layout
private List<List<View>> allLines = new ArrayList<>();
//記錄每一行的行高,用于layout
private List<Integer> lineHeights = new ArrayList<>();
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void initMeasureParams() {
allLines = new ArrayList<>();
lineHeights = new ArrayList<>();
}
/**
* @param widthMeasureSpec flowlayout的父布局measure子布局的時候傳過來引數
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
initMeasureParams();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// Log.d(TAG, "padding=" + paddingLeft + " " + paddingRight+ " " + paddingTop+ " " + paddingBottom);
List<View> lineViews = new ArrayList<>();//保存一行中所有的view
int lineWidthUsed = 0;//記錄這行已經使用了多寬的size
int lineHeight = 0;//一行的行高
int selfWidth = MeasureSpec.getSize(widthMeasureSpec);//viewGroup決議父布局給的寬度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec);
Log.d(TAG, "selfWidth=" + selfWidth + " selfHeight=" + selfHeight);
int parentNeededWidth = 0;//measure程序中,子view要求的父ViewGroup的寬
int parentNeededHeight = 0;
//先度量孩子
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
LayoutParams childLP = childView.getLayoutParams();//拿到xml里面layout_width、layout_height轉成layoutParams
// Log.d(TAG, "childLPW=" + childLP.width + " childLPH=" + childLP.height);
//將layoutParams轉變為measureSpec
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingBottom + paddingTop, childLP.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//獲取子view的度量寬高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
// Log.d(TAG,"childMeasuredWidth="+childMeasuredWidth+" childMeasuredHeight="+childMeasuredHeight);
if (childMeasuredWidth + lineWidthUsed > selfWidth) {
allLines.add(lineViews);
lineHeights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
//view 是分行layout的,所以要記錄每一行有哪些view,這樣可以方便layout布局
lineViews.add(childView);
//每一行都有自己的寬高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasuredHeight);
}
//度量自己
//flowlayout作為viewGroup,他自己也是一個view,他的大小也需要根據他的父親給他提供的寬高來度量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
Log.d(TAG, "widthMode=" + widthMode + " heightMode=" + heightMode);
Log.d(TAG, "parentNeededWidth=" + parentNeededWidth + " parentNeededHeight=" + parentNeededHeight);
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
setMeasuredDimension(realWidth, realHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int curL = getPaddingLeft();
int curT = getPaddingTop();
for (int i = 0; i < allLines.size(); i++) {
List<View> lineViews = allLines.get(i);
int lineHeight = lineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View view = lineViews.get(j);
int left = curL;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom);
curL = right + mHorizontalSpacing;
}
curT = curT + lineHeight + mVerticalSpacing;
curL = getPaddingLeft();
}
}
public static int dp2px(int dp) {
int dp16 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
// Log.d(TAG,"dp="+dp16);
return dp16;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/248118.html
標籤:其他
下一篇:安卓學習日志 — Day06
