Overview
RelativeLayout是Android中一種常用的布局剔交,通過View之間的相互關(guān)系能夠方便地確定子View的位置,通過源碼的學(xué)習(xí)來看下Android是如何實(shí)現(xiàn)這一功能的。Android中的一個(gè)組件灿渴,工作原理主要在onMeasure(),onLayout(),onDraw()這3大階段中叹侄。
onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
sortChildren();
}
// ……
}
測(cè)量過程的第一步是對(duì)子View進(jìn)行排序,在這一步驟會(huì)設(shè)置一個(gè)標(biāo)志位mDirtyHierarchy進(jìn)行優(yōu)化装诡,下面看下sortChildren的具體實(shí)現(xiàn):
private void sortChildren() {
final int count = getChildCount();
if (mSortedVerticalChildren == null || mSortedVerticalChildren.length != count) {
mSortedVerticalChildren = new View[count];
}
if (mSortedHorizontalChildren == null || mSortedHorizontalChildren.length != count) {
mSortedHorizontalChildren = new View[count];
}
final DependencyGraph graph = mGraph;
graph.clear();
for (int i = 0; i < count; i++) {
graph.add(getChildAt(i));
}
graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
}
這個(gè)函數(shù)包含以下步驟:
- 初始化兩個(gè)數(shù)組银受,分別用于記錄水平方向和豎直方向的排序結(jié)果
- 初始化一個(gè)依賴圖DependencyGraph,將子View都加入到依賴圖中
- 調(diào)用DependencyGraph的getSortedViews方法鸦采,將兩個(gè)方向的排序結(jié)果記錄到對(duì)應(yīng)數(shù)組中
下面首先看下DependencyGraph的結(jié)構(gòu):
private static class DependencyGraph {
/**
* List of all views in the graph.
*/
private ArrayList<Node> mNodes = new ArrayList<Node>();
/**
* List of nodes in the graph. Each node is identified by its
* view id (see View#getId()).
*/
private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
/**
* Temporary data structure used to build the list of roots
* for this graph.
*/
private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
}
結(jié)果里每個(gè)成員代表的含義宾巍,其注釋已經(jīng)說清楚了;再具體看下Node的結(jié)構(gòu):
static class Node {
/**
* The view representing this node in the layout.
*/
View view;
/**
* The list of dependents for this node; a dependent is a node
* that needs this node to be processed first.
*/
final ArrayMap<Node, DependencyGraph> dependents =
new ArrayMap<Node, DependencyGraph>();
/**
* The list of dependencies for this node.
*/
final SparseArray<Node> dependencies = new SparseArray<Node>();
}
根據(jù)注釋也能夠明白每個(gè)成員的含義
接下來看下DependencyGraph是如何實(shí)現(xiàn)getSortedViews方法的:
void getSortedViews(View[] sorted, int... rules) {
// 找出所有的不依賴其他節(jié)點(diǎn)的節(jié)點(diǎn)渔伯,即根節(jié)點(diǎn)
final ArrayDeque<Node> roots = findRoots(rules);
int index = 0;
Node node;
// 循環(huán)從根節(jié)點(diǎn)的隊(duì)尾中取出元素
while ((node = roots.pollLast()) != null) {
final View view = node.view;
final int key = view.getId();
// 將該節(jié)點(diǎn)加入排序結(jié)果
sorted[index++] = view;
// 取出該節(jié)點(diǎn)的所有伴隨節(jié)點(diǎn)
final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
final int count = dependents.size();
for (int i = 0; i < count; i++) {
// 遍歷伴隨節(jié)點(diǎn)
final Node dependent = dependents.keyAt(i);
// 取出伴隨節(jié)點(diǎn)的所有依賴節(jié)點(diǎn)集合
final SparseArray<Node> dependencies = dependent.dependencies;
// 將當(dāng)前節(jié)點(diǎn)從伴隨節(jié)點(diǎn)的依賴節(jié)點(diǎn)集合中去掉
dependencies.remove(key);
// 如果該伴隨節(jié)點(diǎn)去掉當(dāng)前節(jié)點(diǎn)的依賴之后顶霞,沒有再依賴其他節(jié)點(diǎn),證明它是新的根節(jié)點(diǎn),加入根節(jié)點(diǎn)集合
if (dependencies.size() == 0) {
roots.add(dependent);
}
}
}
if (index < sorted.length) {
throw new IllegalStateException("Circular dependencies cannot exist"
+ " in RelativeLayout");
}
}
這個(gè)排序的過程已經(jīng)在注釋中說明选浑,實(shí)現(xiàn)過程還是挺明確的蓝厌。
再看完排序過程,我們回到onMeasure()方法中
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ……
int myWidth = -1;
int myHeight = -1;
int width = 0;
int height = 0;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Record our dimensions if they are known;
if (widthMode != MeasureSpec.UNSPECIFIED) {
myWidth = widthSize;
}
if (heightMode != MeasureSpec.UNSPECIFIED) {
myHeight = heightSize;
}
if (widthMode == MeasureSpec.EXACTLY) {
width = myWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = myHeight;
}
View ignore = null;
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
int left = Integer.MAX_VALUE;
int top = Integer.MAX_VALUE;
int right = Integer.MIN_VALUE;
int bottom = Integer.MIN_VALUE;
boolean offsetHorizontalAxis = false;
boolean offsetVerticalAxis = false;
if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
ignore = findViewById(mIgnoreGravity);
}
final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
// We need to know our size for doing the correct computation of children positioning in RTL
// mode but there is no practical way to get it instead of running the code below.
// So, instead of running the code twice, we just set the width to a "default display width"
// before the computation and then, as a last pass, we will update their real position with
// an offset equals to "DEFAULT_WIDTH - width".
final int layoutDirection = getLayoutDirection();
if (isLayoutRtl() && myWidth == -1) {
myWidth = DEFAULT_WIDTH;
}
// ……
}
中間這段是一些輔助變量的初始化古徒,就不多說了拓提,繼續(xù)往下看
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ……
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;
}
}
}
// ……
}
測(cè)量過程的第二步是遍歷子View進(jìn)行水平方向的依賴測(cè)量,這一步通過3個(gè)函數(shù)進(jìn)行隧膘,逐個(gè)看下函數(shù)的實(shí)現(xiàn)代态。
介紹函數(shù)實(shí)現(xiàn)之前先介紹一下rule的概念,在RelativeLayout.LayoutParams進(jìn)行解析時(shí)疹吃,會(huì)將RelativeLayout中的子View的依賴關(guān)系通過rules數(shù)組存儲(chǔ)起來蹦疑,數(shù)組中的下標(biāo)代表某條規(guī)則,例如LEFT_OF萨驶,對(duì)應(yīng)的元素代表這條規(guī)則依賴的View id:
switch (attr) {
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
rules[LEFT_OF] = a.getResourceId(attr, 0);
break;
}
了解了rule的概念之后歉摧,看下遍歷子View進(jìn)行依賴測(cè)量時(shí)相關(guān)方法的實(shí)現(xiàn):
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams;
// VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
// left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
// wants to the right
// left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
// wants to the left
// left=10, right=20 means the left and right ends are both fixed
childParams.mLeft = VALUE_NOT_SET;
childParams.mRight = VALUE_NOT_SET;
anchorParams = getRelatedViewParams(rules, LEFT_OF);
if (anchorParams != null) {
childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
childParams.rightMargin);
} else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
anchorParams = getRelatedViewParams(rules, RIGHT_OF);
if (anchorParams != null) {
childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
childParams.leftMargin);
} else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
if (anchorParams != null) {
childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
} else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
if (anchorParams != null) {
childParams.mRight = anchorParams.mRight - childParams.rightMargin;
} else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
if (0 != rules[ALIGN_PARENT_LEFT]) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
if (0 != rules[ALIGN_PARENT_RIGHT]) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
}
縱觀整個(gè)函數(shù),其實(shí)就是對(duì)水平方向的幾條規(guī)則逐個(gè)進(jìn)行處理腔呜,這幾條規(guī)則分別是:LEFT_OF/RIGHT_OF/ALIGN_LEFT/ALIGN_RIGHT/ALIGN_PARENT_LEFT/ALIGN_PARENT_RIGHT叁温;
對(duì)于每個(gè)屬性的處理方法都是相似的:通過相應(yīng)的規(guī)則從getRelativeViewParams中取到對(duì)應(yīng)依賴的View,然后通過依賴的View的對(duì)應(yīng)屬性和padding來調(diào)整該View的對(duì)應(yīng)屬性
看看下getRelativeViewParams的實(shí)現(xiàn):
private LayoutParams getRelatedViewParams(int[] rules, int relation) {
View v = getRelatedView(rules, relation);
if (v != null) {
ViewGroup.LayoutParams params = v.getLayoutParams();
if (params instanceof LayoutParams) {
return (LayoutParams) v.getLayoutParams();
}
}
return null;
}
真正的實(shí)現(xiàn)在getRelatedView中
private View getRelatedView(int[] rules, int relation) {
int id = rules[relation];
if (id != 0) {
DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
if (node == null) return null;
View v = node.view;
// Find the first non-GONE view up the chain
while (v.getVisibility() == View.GONE) {
rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection());
node = mGraph.mKeyNodes.get((rules[relation]));
// ignore self dependency. for more info look in git commit: da3003
if (node == null || v == node.view) return null;
v = node.view;
}
return v;
}
return null;
}
關(guān)鍵點(diǎn):
- 通過rules數(shù)組取到對(duì)應(yīng)依賴的View id
- 通過View id從依賴圖中取出對(duì)應(yīng)的View
- 如果取到的View可見性為GONE育谬,則繼續(xù)通過對(duì)應(yīng)的依賴規(guī)則取依賴的View所依賴的View(有點(diǎn)拗口...)
經(jīng)過applyHorizontalSizeRules方法的處理之后券盅,子View的params中的left/right值都已經(jīng)確定好,然后會(huì)進(jìn)入measureChildHorizontal方法
private void measureChildHorizontal(
View child, LayoutParams params, int myWidth, int myHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
myWidth);
final int childHeightMeasureSpec;
if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
if (params.height >= 0) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
params.height, MeasureSpec.EXACTLY);
} else {
// Negative values in a mySize/myWidth/myWidth value in
// RelativeLayout measurement is code for, "we got an
// unspecified mode in the RelativeLayout's measure spec."
// Carry it forward.
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
} else {
final int maxHeight;
if (mMeasureVerticalWithPaddingMargin) {
maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
- params.topMargin - params.bottomMargin);
} else {
maxHeight = Math.max(0, myHeight);
}
final int heightMode;
if (params.height == LayoutParams.MATCH_PARENT) {
heightMode = MeasureSpec.EXACTLY;
} else {
heightMode = MeasureSpec.AT_MOST;
}
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在上一階段我們確定了子View的left/right參數(shù)膛檀,在這一階段锰镀,可以通過left/right得到子View的寬度,然后進(jìn)行第一次子View的measure方法調(diào)用咖刃,進(jìn)行第一次測(cè)量
接下來調(diào)用positionChildHorizontal方法:
private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
boolean wrapContent) {
final int layoutDirection = getLayoutDirection();
int[] rules = params.getRules(layoutDirection);
if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) {
// Right is fixed, but left varies
params.mLeft = params.mRight - child.getMeasuredWidth();
} else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// Left is fixed, but right varies
params.mRight = params.mLeft + child.getMeasuredWidth();
} else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// Both left and right vary
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
if (!wrapContent) {
centerHorizontal(child, params, myWidth);
} else {
positionAtEdge(child, params, myWidth);
}
return true;
} else {
// This is the default case. For RTL we start from the right and for LTR we start
// from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
positionAtEdge(child, params, myWidth);
}
}
return rules[ALIGN_PARENT_END] != 0;
}
這個(gè)方法的代碼不長(zhǎng)泳炉,從實(shí)現(xiàn)中可以得知,該方法的作用在于嚎杨,那些在前階段沒有設(shè)置的左右邊界約束花鹅。
接下來是第三步,遍歷子View進(jìn)行垂直方向的依賴測(cè)量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ……
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
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);
}
}
}
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);
}
}
}
// ……
}
垂直方向的約束測(cè)量過程和水平方向是一個(gè)套路:
- 遍歷子View枫浙,根據(jù)垂直方向的規(guī)則進(jìn)行top/bottom的約束
- 測(cè)量一次子View
- 補(bǔ)充邊界約束
- 不同的是刨肃,在垂直方向的測(cè)量階段,還會(huì)對(duì)RelativeLayout的最大寬度和最大高度進(jìn)行計(jì)算
經(jīng)過水平和垂直方向兩輪對(duì)子View的分發(fā)測(cè)量箩帚,已經(jīng)完成了子View的測(cè)量過程真友,接下來是根據(jù)是否wrapContent調(diào)整RelativeLayout自身的高度
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++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final 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;
}
}
}
}
}
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++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final 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;
}
}
}
}
}
然后再根據(jù)Gravity屬性調(diào)整位置
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++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final 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;
}
}
}
}
}
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++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final 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;
}
}
}
}
}
最后就是設(shè)置自身的寬高,完成測(cè)量過程
if (isLayoutRtl()) {
final int offsetWidth = myWidth - width;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mLeft -= offsetWidth;
params.mRight -= offsetWidth;
}
}
}
setMeasuredDimension(width, height);
上述就是onMeasure的過程紧帕。
onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
onLayout的過程很簡(jiǎn)單盔然,遍歷子View,因?yàn)樽覸iew在分發(fā)測(cè)量過程中已經(jīng)確定了上下左右的邊界約束,所以直接調(diào)用對(duì)應(yīng)的約束值進(jìn)行l(wèi)ayout愈案。
onDraw
在RelativeLayout中挺尾,沒有對(duì)onDraw進(jìn)行重寫。
總結(jié)
和大多數(shù)容器布局類似站绪,RelativeLayout的核心在于測(cè)量階段遭铺,需要遍歷兩次子View,原因是水平和垂直方向各需一次來確定相應(yīng)方向的邊界約束恢准。