這是Android視圖繪製系列文章的第二篇,系列文章目錄以下:算法
若是想直接看結論能夠移步到第三篇末尾。bash
View繪製就比如畫畫,先拋開Android概念,若是要畫一張圖,首先會想到哪幾個基本問題:ide
Android繪製系統也是按照這個思路對View進行繪製,上面這些問題的答案分別藏在:函數
這一篇將從源碼的角度分析「定位(layout)」。佈局
位置都是相對的,好比「我在你的右邊」、「你在廣場的西邊」。爲了代表位置,老是須要一個參照物。View
的定位也須要一個參照物,這個參照物是View
的父控件。能夠在View
的成員變量中找到以下四個描述位置的參數:post
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
/**
* The distance in pixels from the left edge of this view’s parent
* to the left edge of this view.
* view左邊相對於父親左邊的距離
*/
protected int mLeft;
/**
* The distance in pixels from the left edge of this view‘s parent
* to the right edge of this view.
* view右邊相對於父親左邊的距離
*/
protected int mRight;
/**
* The distance in pixels from the top edge of this view’s parent
* to the top edge of this view.
* view上邊相對於父親上邊的距離
*/
protected int mTop;
/**
* The distance in pixels from the top edge of this view‘s parent
* to the bottom edge of this view.
* view底邊相對於父親上邊的距離
*/
protected int mBottom;
...
}
複製代碼
View
經過上下左右四條線圍城的矩形來肯定相對於父控件的位置以及自身的大小。 那這裏所說的大小和上一篇中測量出的大小有什麼關係呢?留個懸念,先看一下上下左右這四個變量在哪裏被賦值。ui
全局搜索後,找到下面這個函數:this
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
/**
* Assign a size and position to this view.
* 賦予當前view尺寸和位置
*
* This is called from layout.
* 這個函數在layout中被調用
*
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
* @return true if the new size and position are different than the previous ones
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
}
複製代碼
沿着調用鏈繼續往上查找:spa
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
/**
* Assign a size and position to a view and all of its
* descendants
* 將尺寸和位置賦予當前view和全部它的孩子
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
* 子類不該該重載這個方法,而應該重載onLayout(),而且在其中局部全部孩子
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
public void layout(int l, int t, int r, int b) {
...
//爲View上下左右四條線賦值
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
//若是佈局改變了則從新佈局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
}
...
/**
* Called from layout when this view should
* assign a size and position to each of its children.
* 當須要賦予全部孩子尺寸和位置的時候,這個函數在layout中被調用
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* 帶有孩子的子類應該重載這個方法並調用每一個孩子的layout()
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
}
複製代碼
結合調用鏈和代碼註釋,能夠得出結論:孩子的定位是由父控件發起的,父控件會在ViewGroup.onLayout()
中遍歷全部的孩子並調用它們的View.layout()
以設置孩子相對於本身的位置。code
不一樣的ViewGroup
有不一樣的方式來佈局孩子,以FrameLayout
爲例:
public class FrameLayout extends ViewGroup {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
//佈局全部孩子
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍歷全部孩子
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//排除不可見孩子
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//得到孩子在measure過程當中肯定的寬高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//肯定孩子左邊相對於父控件位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
//肯定孩子上邊相對於父控件位置
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//調用孩子的layout(),肯定孩子相對父控件位置
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
}
複製代碼
FrameLayout
全部的孩子都是相對於它的左上角進行定位,而且在定位孩子右邊和下邊的時候直接加上了在measure過程當中獲得的寬和高。
在FrameLayout
遍歷孩子並觸發它們定位的過程當中,會用到上一篇測量的結果(經過getMeasuredWidth()
和getMeasuredHeight()
),並最終經過layout()
影響mRight
和mBottom
的值。對比一下getWidth()
和getMeasuredWidth()
:
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
public final int getWidth() {
//控件右邊和左邊差值
return mRight - mLeft;
}
/**
* Like {@link #getMeasuredWidthAndState()}, but only returns the
* raw width component (that is the result is masked by
* 得到MeasureSpec的尺寸部分
* {@link #MEASURED_SIZE_MASK}).
*
* @return The raw measured width of this view.
*/
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
}
複製代碼
getMeasuredWidth()
是measure過程的產物,它是測量尺寸。getWidth()
是layout過程的產物,它是佈局尺寸。它們的值可能不相等。ViewGroup
會有不一樣的layout算法,也就有不一樣的使用參考值的方法,控件最終展現尺寸由layout過程決定(以佈局尺寸爲準)。mTop
)、下(mBottom
)、左(mLeft
)、右(mRight
)四條線圍城的矩形來描述的。onLayout()
,在其中遍歷全部孩子並調用它們的layout()
方法以肯定子控件相對於本身的位置。View.setFrame()
的調用,它表示着視圖矩形區域的大小以及相對於父控件的位置已經肯定。