[Android FrameWork 6.0源碼學習] View的重繪過程之Layout
View繪製的三部曲,測量,布局,繪畫
現在我們分析布局部分
測量部分在上篇文章中已經分析過了。不了解的可以去我的博客里找一下
View的布局和測量一樣,都是從ViewRootImpl中發起,ViewRootImpl先通過measure來初始化整個的view樹
之後會調用onLayout方法來布局,ViewRootImpl是通過performLayout函數來發起重繪的
比較重要的部分我會寫注釋,注意看注釋就行
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth + ", " + host.getMeasuredHeight + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//通過調用DecorView的layout函數,來發起整個view視圖的重繪
host.layout(0, 0, host.getMeasuredWidth, host.getMeasuredHeight);
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size;
if (numViewsRequestingLayout > 0) {
// requestLayout was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size;
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout;
}
measureHierarchy(host, lp, mView.getContext.getResources,
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth, host.getMeasuredHeight);
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop"d
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue.post(new Runnable {
@Override
public void run {
int numValidRequests = finalRequesters.size;
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout;
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
這個函數主要功能就是調用view的layout方法,接下來要分析的就是layout函數了。這個函數在View中,是觸發onLayout函數的方法
@SuppressWarnings({"unchecked"})
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;
//判斷是否使用 optical bound 布局,並且繪製Frame出來
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果需要重新layout的話,就開始調用DecorView的onLayout方法,我們簡單看一下
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;
}
這個函數的工作就是分發整個的布局流程,先是DecorView,在FrameLayout ....直到整個view tree布局完畢
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//獲取界面的邊框如果有偏移,就需要偏移一下view窗口
getOutsets(mOutsets);
if (mOutsets.left > 0) {
offsetLeftAndRight(-mOutsets.left);
}
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
}
這個onLayout是在DecorView中,他調用了super,也就是FrameLayout下邊的onLayout方法,我們在繼續看FrameLayout
/**
* {@inheritDoc}
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
這個函數頁很簡單,直接調用了layoutChildren方法去布局各種子view
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;
//開始布局,目前這個是FrameLayout,特性就是默認左上角,且會z軸覆蓋
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;
}
//布局子view,以此類推,會布局完整個view樹
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
上面方法運行完後,整個的布局過程就結束了。view這塊的設計非常棒,採用了組合模式去設計,在上邊循環中去調用layout方法,layout在去觸發子view的onLayout來按照各自的規則去布局,直到整個view樹循環完畢
※mysql 5.7 root密碼重置(centos 7)
※node調用phantomjs-node爬取複雜頁面
※關於canvas畫布使用fillRect()時高度出現雙倍效果解決辦法
※Eclipse中安裝MemoryAnalyzer插件及使用
※用Eclipse生成JPA元模型
TAG:達人科技 |
※網關 Spring-Cloud-Gateway 源碼解析——路由之RouteDefinitionLocator一覽
※閱讀Android源碼BitmapFactory
※Selenium3源碼之common package篇
※RocketMQ 源碼學習 2 : Namesrv
※React Native BackHandler exitApp 源碼分析
※HikariCP源碼分析之leakDetectionThreshold及實戰解決Spark/Scala連接池泄漏
※Execotors、Future、Callable和FutureTask的使用與源碼分析
※RocketMQ 源碼學習 3 :Remoting 模塊
※druid-spring-boot-starter源碼解析
※Apache Storm流計算模型 及WordCount源碼實踐
※kafka 源碼分析 3 : Producer
※Flutter圖片緩存 Image.network源碼分析
※JDK源碼閱讀:InterruptibleChannel 與可中斷 IO
※JDK 源碼閱讀 : DirectByteBuffer
※Istio技術與實踐02:源碼解析之Istio on Kubernetes 統一服務發現
※jQuery源碼分析之jQuery.event.fix方法五問
※Spark 源碼分析之ShuffleMapTask內存數據Spill和合併
※從JDK源碼看StringBuffer
※Kafka 源碼分析 5 :KafkaConsumer 消費處理
※mybaits sqlSession 源碼解讀