View繪製——畫在哪?
這是Android檢視繪製系列文章的第一篇,系列文章目錄如下:
View繪製就好比畫畫,先拋開Android概念,如果要畫一張圖,首先會想到哪幾個基本問題:
- 畫多大?
- 畫在哪?
- 畫什麼?
Android繪製系統也是按照這個思路對View進行繪製,上面這些問題的答案分別藏在:
- 測量(measure)
- 定位(layout)
- 繪製(draw)
這一篇將從原始碼的角度分析“定位(layout)”。
如何描述位置
位置都是相對的,比如“我在你的右邊”、“你在廣場的西邊”。為了表明位置,總是需要一個參照物。View
的定位也需要一個參照物,這個參照物是View
的父控制元件。可以在View
的成員變數中找到如下四個描述位置的引數:
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
通過上下左右四條線圍城的矩形來確定相對於父控制元件的位置。
確定相對位置
那這四個點在哪裡被賦值?全域性搜尋後,找到下面這個函式:
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; ... } }
沿著呼叫鏈繼續往上查詢:
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()
以設定孩子相對於自己的位置。
不同的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); } } } }
總結
layout()