Android GUI之View佈局
在清楚了View繪製機制中的第一步測量之後,我們繼續來了解分析View繪製的第二個過程,那就是佈局定位。繼續跟蹤分析原始碼,根據之前的流程分析我們知道View的繪製是從RootViewImpl的performTraversals方法開始的,在此方法中依次呼叫了performMeasure、performLayout、performDraw等方法進行測量、佈局、繪製,那麼下面我們就看看則方performLayout中都做了哪些事情,該方法的關鍵原始碼如下:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; …… try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); …… } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
從中看出,最關鍵的程式碼就是呼叫了host.layout方法,那麼大家還記不記得host是個什麼東東呢?對了,正是我們之前說的根檢視DecorView。那麼我們就回到DecorView看看它在layout方法中到底做了什麼事情。令人失望的是,我們在DecorView中並沒有發現該方法,不要急,根據該類的繼承體系,我們最終追蹤到layout方法在View中。
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; 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); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; } protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
該方法中的4個引數代表了當前的View與父View之間4個方向上的距離,同時從說明中可以看出,此方法不應該被子類重寫,如果需要重新佈局,可以在子類中重寫的方法是onLayout,此方法在View中是個空方法,什麼都沒有寫。可實際上layout的方法在View中並沒有被標識為final,這就意味是可以被重寫的。
繼續檢視ViewGoup中的相關程式碼,果然layout被重寫了並新增了final標識,同時onLayout被標識為抽象方法,所以繼承了ViewGroup的類是,是不能重寫layout方法的,並且要實現onLayout方法。從程式碼可以看出,雖然ViewGroup重寫了layout,實際本質上還是呼叫了View的layout,然後通過呼叫onLayout方法最終完成佈局定位的。
@Override public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { if (mTransition != null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; } } @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
在DecorView中,並沒有發現onLayout方法,所以它使用的肯定是其父類FrameLayout中的,找到FrameLayout的原始碼,可以檢視到onLayout方法,具體如下:
@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(); mForegroundBoundsChanged = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 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; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
從方法中,我們可以看出在Framelayout中最終呼叫了layoutChildren方法,在該方法中根據測量結果和一些佈局屬性對容器中每一個View都呼叫了layout方法進行了佈局。根據以上的程式碼分析,我們可以得出View佈局定位的流程圖如下。
相關文章
- Android XML佈局報錯:android/view/View$OnUnhandledKeyEventListenerAndroidXMLView
- Android View 佈局流程(Layout)完全解析AndroidView
- Android自定義View(四)側滑佈局AndroidView
- 你需要知道的Android View的佈局AndroidView
- Android之TableLayout(表格佈局)Android
- Android之佈局屬性Android
- Android中View的測量和佈局過程AndroidView
- Android中View的量算、佈局及繪圖機制AndroidView繪圖
- PlayMaker GUI跟隨佈局的使用GUI
- PlayMaker佈局技巧:預覽GUI介面GUI
- Android開發之常用佈局Android
- Android 佈局優化之includeAndroid優化
- 原始碼解析Android中View的layout佈局過程原始碼AndroidView
- Android入門教程 | UI佈局之RelativeLayout 相對佈局AndroidUI
- Android優化之佈局優化Android優化
- Android入門教程 | UI佈局之LinearLayout 線性佈局AndroidUI
- Android 佈局Android
- 關於Android中xml佈局檔案之android 入門xml佈局檔案AndroidXML
- Android自定義View實現流式佈局(熱門標籤效果)AndroidView
- Android效能優化之佈局優化Android優化
- Android系統修改之Notification佈局修改Android
- Android學習之 UI佈局優化AndroidUI優化
- self.view.frame的佈局問題View
- Android佈局概述Android
- Android xml 佈局AndroidXML
- 三欄佈局之自適應佈局
- 移動佈局基礎之 流式佈局
- CSS經典佈局之Sticky footer佈局CSS
- Android中常見的佈局和佈局引數Android
- android佈局------RelativeLayout(相對佈局)詳解Android
- android筆記二(水平佈局與垂直佈局)Android筆記
- CSS 三欄佈局之聖盃佈局和雙飛翼佈局CSS
- CSS:三欄佈局之雙飛翼佈局CSS
- Android學習—— Android佈局Android
- Android 佈局優化Android優化
- android 介面佈局(大概)Android
- CSS之居中佈局CSS
- ionic之基本佈局