1. 核心成員與標(biāo)記
1. LEFT_OF, RIGHT_OF,ABOVE,BELOW: 聲明某個子child相對于另外一個子child的位置。
2. ALIGN_BASELINE:某個child和另一個child的基準(zhǔn)線對齊。
3. ALIGN_LEFT,ALIGN_TOP,ALIGN_RIGHT,ALIGN_BOTTOM:某個child和另外一個child的對齊方式,左對齊,右對齊,上對齊,下對齊。
4. ALIGN_PARENT_LEFT,ALIGN_PARENT_TOP,ALIGN_PARENT_RIGHT,ALIGN_PARENT_BOTTOM:某個child和RelativeLayout的父容器的對齊方式。左,右,上,下對齊。
5. CENTER_IN_PARENT:child位于parent的正中間布局。
6. CENTER_HORIZONTAL:child位于parent的水平中間布局。
7. CENTER_VERTICAL:child位于parent的垂直中間布局。
8. START_OF,END_OF,ALIGN_START,ALIGN_END, ALIGN_PARENT_START,ALIGN_PARENT_END:相對于上面的left/end, align_left/align_right等考慮了相對方向,也就是從右到左的布局。
9. RULES_VERTICAL:包含了ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM,他的意思是子child之間互相依賴來定位的標(biāo)記。特指縱向哦。
10. RULES_HORIZONTAL:包含了LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END,他的意思是子child之間需互相依賴來定位的標(biāo)記,特指橫向哦。
11. gravity屬性:可以指定子view為left,top, right, bottom等位置擺放。
2. 內(nèi)部類
-
RelativeLayout.LayoutParams
這是一個給RelativeLayout的子child提供的布局參數(shù)。
- mRules: 在LayoutParams的構(gòu)造函數(shù)中記錄了left, right, align, start等各種標(biāo)記過的view元素的id.
- resolveRules(): 將設(shè)定的start, end等布局方向相關(guān)的屬性轉(zhuǎn)換成對應(yīng)的lef, right。注意的是如果start和left同時設(shè)定了,那么優(yōu)先考慮start.end如right同理。這里還考慮到targetversion是低于17, 那么start, end等類似的屬性是無效的,將他們直接轉(zhuǎn)換成left, right.施加的布局方向是沒有作用的呢。
- resolveLayoutDirection:給LayoutParams設(shè)定布局方向,同時他還會調(diào)用上面的resolveRules重新解析Relativelayout的布局策略。
-
RelativeLayout.DependencyGraph
主要目的是為RelativeLayout確定view的定位布局的順序,即先布局哪個,后布局哪個元素。將這些確定順序的view放置到一個容器中,這里面有依賴的關(guān)系線。
mNodes:記錄了所有的view。
mKeyNodes: 和上面的一樣記錄了所有的view, 但是以key-value的方式,key為view的id。
mRoots: 本意是記錄一些不需要依賴其他view的位置來定位自己的view. 經(jīng)過內(nèi)部處理后和依賴關(guān)系的移除之后,里面就是mNodes中所有的view了。
-
DependencyGraph.findRoots方法
該方法的意圖是根據(jù)縱向或者橫向的依賴標(biāo)記來找出不依賴其他child定位的child
private ArrayDeque<Node> findRoots(int[] rulesFilter) { final SparseArray<Node> keyNodes = mKeyNodes; final ArrayList<Node> nodes = mNodes; final int count = nodes.size(); // Find roots can be invoked several times, so make sure to clear // all dependents and dependencies before running the algorithm for (int i = 0; i < count; i++) { final Node node = nodes.get(i); node.dependents.clear(); node.dependencies.clear(); } // Builds up the dependents and dependencies for each node of the graph for (int i = 0; i < count; i++) { final Node node = nodes.get(i); final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams(); final int[] rules = layoutParams.mRules; final int rulesCount = rulesFilter.length; //1.這里是關(guān)鍵的地方,根據(jù)rulesFilter選擇的標(biāo)記,來找到當(dāng)前view依賴的child-view。找到了依賴之后。把當(dāng)前view添加到他的附屬集合里面去,把依賴view添加到當(dāng)前view的附主集合里面去。 for (int j = 0; j < rulesCount; j++) { final int rule = rules[rulesFilter[j]]; if (rule > 0) { // The node this node depends on final Node dependency = keyNodes.get(rule); // Skip unknowns and self dependencies if (dependency == null || dependency == node) { continue; } // Add the current node as a dependent dependency.dependents.put(node, this); // Add a dependency to the current node node.dependencies.put(rule, dependency); } } } final ArrayDeque<Node> roots = mRoots; roots.clear(); //遍歷所有的view, 把那些沒有附主依賴項的添加到root集合。也就是說這些view可以是根view一樣,可以首先定位。 // Finds all the roots in the graph: all nodes with no dependencies for (int i = 0; i < count; i++) { final Node node = nodes.get(i); if (node.dependencies.size() == 0) roots.addLast(node); } return roots; }
-
DependencyGraph.getSortedViews方法
填充mRoots集合,他會將那些依賴其他兄弟view定位的view的陸續(xù)填入進去,它會先將被依賴的view放入到root中表示被依賴方已經(jīng)穩(wěn)定定位, 然后放入自己,確定這樣的定位順序。如果知道拓撲排序的話,就很容易理解這個過程啦, 拓撲排序就是按照依賴關(guān)系去排序,讓排在前面的內(nèi)容不會依賴排在后面的內(nèi)容,這和我們的mRoot之中的依賴圖譜是一致的哦.
void getSortedViews(View[] sorted, int... rules) { //找到第一批不需要依賴其他child定位的view集合。 final ArrayDeque<Node> roots = findRoots(rules); int index = 0; Node node; // 遍歷root. while ((node = roots.pollLast()) != null) { final View view = node.view; final int key = view.getId(); //root的節(jié)點直接添加進去,他們是無依賴的。 sorted[index++] = view; //獲取當(dāng)前節(jié)點的附屬項,假如B,C依賴A定位。那么A的的dependents就是B,C final ArrayMap<Node, DependencyGraph> dependents = node.dependents; final int count = dependents.size(); //遍歷B, C附屬。 for (int i = 0; i < count; i++) { final Node dependent = dependents.keyAt(i); //獲取B/C的附主方,這里就是A. final SparseArray<Node> dependencies = dependent.dependencies; //移除A附主方,因為A已經(jīng)sort排好序了,這樣B/C就可以說依賴的項就已知了。 dependencies.remove(key); //如果這里的附主方清0了,那么就可以作為root節(jié)點添加到root里面了。 //比如B除了依賴A, 可能還依賴D,那么要等下一次定位D之后才能將A-add進去哦。 if (dependencies.size() == 0) { roots.add(dependent); } } }
graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL); graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL); - 這兩行代碼意圖是將所有的子child從橫縱兩個方向來計算view的定位順序,也即mSortedHorizontalChildren, mSortedVerticalChildren。他里面的元素是按照定位順序來排列的。
- Node
- dependents: 附屬內(nèi)容,如果b依賴a定位, 那么b是a的附屬。a里面的dependents就是b這類元素。
- dependencies: 附主內(nèi)容,如果b依賴a定位,那么a是b的附主。這個屬性集合里面就是b所有依賴的附主方。
- View: 就是當(dāng)前的節(jié)點的view。
3. 核心方法分解
onLayout: 因為RelativeLayout都是子child相對性地布局,所以只要計算出子child的坐標(biāo), 就可以知道child放置在什么位置。
sortChildren:生成兩個集合,分別是縱行和橫向的view集。他們都是RelativeLayout的子child全集,按照定位的順序,排列成一個集合。后面的測量就從中取出一個個地測量啦。
-
onMeasue
RelativeLayout的核心算法,它分橫向,縱行兩個方向來測量RelativeLayout和子view, 這也是為什么說relativeLayout相對比較LinearLayout耗費性能, 它的邏輯概括起來分為四步:
- 橫向的測量:定位,測量,計算最終位置;
- 縱向的測量: 定位,測量,計算最終位置;
- isWrapContentHeight, 修正高度
- isWrapContentWidth,修正寬度
注意:MeasureSpec.UNSPECIFIED,這個是測量模式在系統(tǒng)中使用構(gòu)建的時候,一般和0一起組合使用,他表明當(dāng)前view的測量規(guī)格是未知的。父容器沒有給我明確的限制所以是UNSPECIFIED,我自己也不知道自己該多大所以是0,具體我的大小到我的具體實現(xiàn)中再去計算吧。這在relativelayout橫豎測量的時候,對測量另外一個相對的數(shù)值有作用。
if (params.width == LayoutParams.MATCH_PARENT) { //這里解釋了為什么當(dāng)RelativeLayout是wrap的時候,子child是match的時候,得到的高度是精確模式,且尺寸為父容器最大。 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST); }
-
onMeasue方法主體
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mDirtyHierarchy) { mDirtyHierarchy = false; //計算縱橫兩個集合。 sortChildren(); } ...... //橫向測量 View[] views = mSortedHorizontalChildren; int count = views.length; for (int i = 0; i < count; i++) { View child = views[i]; if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); int[] rules = params.getRules(layoutDirection); applyHorizontalSizeRules(params, myWidth, rules); measureChildHorizontal(child, params, myWidth, myHeight); if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) { offsetHorizontalAxis = true; } } } //縱向測量 views = mSortedVerticalChildren; count = views.length; final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; for (int i = 0; i < count; i++) { View child = views[i]; if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); //應(yīng)用規(guī)則,找到child的top或者bottom位置。 applyVerticalSizeRules(params, myHeight); //測量child的寬與高。 measureChild(child, params, myWidth, myHeight); if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) { offsetVerticalAxis = true; } //如果是wrap的寬,計算出RelativeLayout的最大寬度1。 if (isWrapContentWidth) { if (isLayoutRtl()) { if (targetSdkVersion < Build.VERSION_CODES.KITKAT) { width = Math.max(width, myWidth - params.mLeft); } else { width = Math.max(width, myWidth - params.mLeft - params.leftMargin); } } else { if (targetSdkVersion < Build.VERSION_CODES.KITKAT) { width = Math.max(width, params.mRight); } else { width = Math.max(width, params.mRight + params.rightMargin); } } } //如果是wrap的高,計算出RelativeLayout的最大高度1。 if (isWrapContentHeight) { if (targetSdkVersion < Build.VERSION_CODES.KITKAT) { height = Math.max(height, params.mBottom); } else { height = Math.max(height, params.mBottom + params.bottomMargin); } } if (child != ignore || verticalGravity) { left = Math.min(left, params.mLeft - params.leftMargin); top = Math.min(top, params.mTop - params.topMargin); } if (child != ignore || horizontalGravity) { right = Math.max(right, params.mRight + params.rightMargin); bottom = Math.max(bottom, params.mBottom + params.bottomMargin); } } } ....... //wrap的寬,矯正前面positionChildHorizontal返回true的child的left,right屬性。 if (isWrapContentWidth) { // Width already has left padding in it since it was calculated by looking at // the right of each child view width += mPaddingRight; if (mLayoutParams != null && mLayoutParams.width >= 0) { width = Math.max(width, mLayoutParams.width); } width = Math.max(width, getSuggestedMinimumWidth()); width = resolveSize(width, widthMeasureSpec); if (offsetHorizontalAxis) { for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); final int[] rules = params.getRules(layoutDirection); if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { centerHorizontal(child, params, width); } else if (rules[ALIGN_PARENT_RIGHT] != 0) { final int childWidth = child.getMeasuredWidth(); params.mLeft = width - mPaddingRight - childWidth; params.mRight = params.mLeft + childWidth; } } } } } //wrap的高,矯正前面positionChildVertical返回true的child的top,bottom屬性。 if (isWrapContentHeight) { // Height already has top padding in it since it was calculated by looking at // the bottom of each child view height += mPaddingBottom; if (mLayoutParams != null && mLayoutParams.height >= 0) { height = Math.max(height, mLayoutParams.height); } height = Math.max(height, getSuggestedMinimumHeight()); height = resolveSize(height, heightMeasureSpec); if (offsetVerticalAxis) { for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); final int[] rules = params.getRules(layoutDirection); if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { centerVertical(child, params, height); } else if (rules[ALIGN_PARENT_BOTTOM] != 0) { final int childHeight = child.getMeasuredHeight(); params.mTop = height - mPaddingBottom - childHeight; params.mBottom = params.mTop + childHeight; } } } } } //默認這個都是false,但是如果relativelayout設(shè)置了Gravity.END, Gravity.Bottom, //那么就會將內(nèi)容右邊或者底部頂邊,這時候就需要矯正child的left, child的right, top, bottom屬性呢。因為前面的left, right,top, bottom屬性都是從start, top這個頂邊位置開始計算的。 if (horizontalGravity || verticalGravity) { final Rect selfBounds = mSelfBounds; selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight, height - mPaddingBottom); final Rect contentBounds = mContentBounds; Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds, layoutDirection); final int horizontalOffset = contentBounds.left - left; final int verticalOffset = contentBounds.top - top; if (horizontalOffset != 0 || verticalOffset != 0) { for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE && child != ignore) { LayoutParams params = (LayoutParams) child.getLayoutParams(); if (horizontalGravity) { params.mLeft += horizontalOffset; params.mRight += horizontalOffset; } if (verticalGravity) { params.mTop += verticalOffset; params.mBottom += verticalOffset; } } } } } if (isLayoutRtl()) { final int offsetWidth = myWidth - width; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); params.mLeft -= offsetWidth; params.mRight -= offsetWidth; } } } //設(shè)定RelativeLayout的最終的寬與高。 setMeasuredDimension(width, height); }
getChildMeasureSpec: 這個和viewGroup中的同名方法的實現(xiàn)不一樣哦,google工程師這里重新實現(xiàn)了一套邏輯。但是感覺這些邏輯有些亂啊,簡單總結(jié)下他的基本邏輯。
如果childStart, childEnd都設(shè)定了,childSpecMode=EXACTLY, childSpecSize=childEnd-childStart
否則,childSize>0, childSpecMode=EXACTLY, childSpecSize是maxAvailable和childSize中最小的一個。
-
否則,childSize == LayoutParams.MATCH_PARENT:
childSpecMode = MeasureSpec.EXACTLY; childSpecSize = maxAvailable; - 這里也可以看出不關(guān)心父容器本身的模式,只要我是MATCH_PARENT,結(jié)果就是EXACTLY,這個和viewGroup的計算是大不同的哦。
-
childSize == LayoutParams.WRAP_CONTENT:
childSpecMode = MeasureSpec.AT_MOST; childSpecSize = maxAvailable;
-
applyHorizontalSizeRules:計算child的left或者right位置。
- 找到它依賴定位的view,然后根據(jù)rulues規(guī)則計算出他的left, 或right的數(shù)值。 - 當(dāng)規(guī)則是ALIGN_PARENT_RIGHT時候,計算出的childParams.mRight是根據(jù)myWidth來的,這個時候的myWidth并不一定是確定的,如果是RelativeLayout是Wrap_conent,那么在后面確定了width之后要重新再計算一次childParams.mRight。
-
measureChildHorizontal: 測量child的寬度,高度也測量但不是最終的結(jié)果。
- 根據(jù)params.mLeft,params.mRight或者childWith來計算出child的寬度測量規(guī)格childWidthMeasureSpec - 根據(jù)myHeight來和params.width的模式得出高度的測量規(guī)格childHeightMeasureSpec 注意:這時候的childHeightMeasureSpec時候不準(zhǔn)確的,只是為了方便測寬度,大概出的一個值。 - 測量child.
-
positionChildHorizontal:根據(jù)left/right以及前面測量的寬度來計算child的位置。
- 因為之前經(jīng)過了寬度的測量,那么寬度是已知的了,然后通過這個寬度來得出child的left, right的位置。方便后續(xù)的layout. - 如果是CENTER_IN_PARENT,CENTER_HORIZONTAL,ALIGN_PARENT_END的三種該方法返回true.表示left,right的位置計算依賴了不確定的父容器,所以等到父容器確定之后需要再定一次left,right.
-
applyVerticalSizeRules:計算child的top或者bottom位置。
- 和measureChildHorizontal同理,計算出縱向的top, bottom
-
measureChild: 測量child的最終的寬與高。
- 得出寬度的計算規(guī)格是childWidthMeasureSpec, 寬度其實前面已經(jīng)計算好了。 - 得出高度的計算規(guī)格, 高度是childHeightMeasureSpec,經(jīng)過前面的定位輔助,以及考慮child的height. - child.measure(childWidthMeasureSpec, childHeightMeasureSpec);最終的測量寬與高。
positionChildVertical:根據(jù)left/right以及前面測量的寬度來計算child的位置。
-
有了前面的寬,高,然后就可以定位child, 可以獲取到他的top,bottom屬性。
- 如果是CENTER_IN_PARENT,CENTER_HORIZONTAL,ALIGN_PARENT_END的三種該方法返回true.表示left,right的位置計算依賴了不確定的父容器,所以等到父容器確定之后需要再定一次top,bottom.
4. 總結(jié)
- RelativeLayout的重點在測量處,缺點也是在這里。對于子控件都要橫豎測量兩遍,這是一個較為耗費性能的地方,所以平時如果可以盡量不要用這個控件哦, 用LinearLayout取代啦。