當前位置:
首頁 > 知識 > [Android FrameWork 6.0源碼學習] View的重繪過程之Layout

[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 源碼解讀