上次我們對View的測量過程有了了解,接著這次肯定就是要沿著View的3大流程往下走。我們本次的主角就是View的Layout
過程。
從performLayout()
開始出發(fā)
我們的已經(jīng)知道了View的布局過程layout pass
就是從performLayout()
開始的,那么我們就先來大概的瀏覽一下這個函數(shù)的代碼。
TIP: 這里我們先簡略的瀏覽整個函數(shù)的代碼(不用認真看),后面我們會將代碼分開幾個部分,一個部分一個部分的進行分析。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 先將當前是否有布局請求的flag設置為false
mLayoutRequested = false;
mScrollMayChange = true;
// 將當前正在布局的flag設置為true
mInLayout = true;
// 獲取decorView
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進行布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 將當前正在布局的flag設置為false
mInLayout = false;
// 獲取當前請求布局的View的個數(shù)
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.
// 獲取需要進行布局的View
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
// 設置正在處理布局請求的flag為true
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
// 獲取需要進行布局的View的個數(shù)
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;
}
performLayout()
的代碼仔細看起來也并不難,我們一點一點的來進行分析。我們都知道這個函數(shù)是在ViewRootImpl
的performTravers()
進行調(diào)用的。那我們來看看它的傳入?yún)?shù)。
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
分別是當前的窗口布局參數(shù)lp
,窗口的寬度和高度desiredWindowWidth,desiredWindowHeight
。
既然知道了參數(shù)的意義,那我們就可以對performLayout()
進行分析了。首先我們就來分析下面部分的代碼(為了看起來更加的清晰,我將一些無關的代碼去掉了,去掉的部分我用省略號代替):
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
.........
mInLayout = true;
final View host = mView;
........
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
.....
這里我們可以看到首先是設置了mInLayout = true
,表示當前正在布局的過程當中。隨后final View host = mView
獲取了我們的decorView
,然后就調(diào)用了我們host.layout()
對我們的decorView
進行布局。布局完成后就將我們的mInLayout
設置為false
表示當前不在布局過程中。這里我們需要關心的就只有host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())
這句代碼,但這并不急于一時,我們先將performLayout()
整個函數(shù)的邏輯分析清楚后再對里面的內(nèi)容進行深入。
那么我們就接著看performLayout()
的下面的代碼(和上面一樣的,我只保留了關鍵的代碼):
Tip:下面的代碼可以先簡略的掃一眼,后面我會詳細的分析
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
mHandlingLayoutInLayoutRequest = true;
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
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);
.........
view.requestLayout();
}
}
});
}
}
}
..........
mInLayout = false;
}
上面的代碼有個地方讓我非常的在意,就是mLayoutRequesters
這個屬性變量,因為后面有幾次都用到了這個變量。這是一個ArrayList<View>
類型的一個集合,從變量名來看,這應該是一個存放著需要進行布局請求的View的集合。但這只是我的猜測,然后我就在ViewRootImpl
類的代碼中搜索了這個變量出現(xiàn)的地方。發(fā)現(xiàn)了一個函數(shù)requestLayoutDuringLayout()
,我們可以來看看這個函數(shù)的代碼:
/**
* Called by {@link android.view.View#requestLayout()} if the view hierarchy is currently
* undergoing a layout pass. requestLayout() should not generally be called during layout,
* unless the container hierarchy knows what it is doing (i.e., it is fine as long as
* all children in that container hierarchy are measured and laid out at the end of the layout
* pass for that container). If requestLayout() is called anyway, we handle it correctly
* by registering all requesters during a frame as it proceeds. At the end of the frame,
* we check all of those views to see if any still have pending layout requests, which
* indicates that they were not correctly handled by their container hierarchy. If that is
* the case, we clear all such flags in the tree, to remove the buggy flag state that leads
* to blank containers, and force a second request/measure/layout pass in this frame. If
* more requestLayout() calls are received during that second layout pass, we post those
* requests to the next frame to avoid possible infinite loops.
*
* <p>The return value from this method indicates whether the request should proceed
* (if it is a request during the first layout pass) or should be skipped and posted to the
* next frame (if it is a request during the second layout pass).</p>
*
* @param view the view that requested the layout.
*
* @return true if request should proceed, false otherwise.
*/
boolean requestLayoutDuringLayout(final View view) {
if (view.mParent == null || view.mAttachInfo == null) {
// Would not normally trigger another layout, so just let it pass through as usual
return true;
}
if (!mLayoutRequesters.contains(view)) {
mLayoutRequesters.add(view);
}
if (!mHandlingLayoutInLayoutRequest) {
// Let the request proceed normally; it will be processed in a second layout pass
// if necessary
return true;
} else {
// Don't let the request proceed during the second layout pass.
// It will post to the next frame instead.
return false;
}
}
這個函數(shù)的注釋,官方已經(jīng)給出了很明確的解析。但有的同學可能英語不太好,我還是先簡單的來講一下注釋的內(nèi)容吧。
首先這個函數(shù)是在view tree
正在進行的布局傳遞過程layout pass
的時候由requestLayout()
調(diào)用的。但通常情況下requestLayout()
不會在布局過程中調(diào)用,除非container hierarchy
(就是整個View樹的層級)知道它自己當前的狀態(tài)(例如當container hierarchy
中所有的子View都完成了測量和在布局過程layout pass
結(jié)束時完成布局的狀態(tài)下是可以調(diào)用的)。如果requestLayout()
在一個frame
(這里的frame
應該是View的一次繪制流程,即一次完整的measure,layout,draw
過程)進行的過程中被調(diào)用了,我們需要將所有要求進行布局的View進行注冊(記錄起來)。在frame
結(jié)束的時候,我們檢查注冊過的View,看看是否還有那些沒有被正確的處理View進行布局請求。如果有的話,我們將view tree
的所有標記清除,得到一個空白的容器,然后在本次frame
強制的執(zhí)行第二次request/measure/layout
過程。如果在第二次布局時還收到requestLayout()
的調(diào)用,我們把這次的布局請求延遲到下一個frame
,以此來避免進入死循環(huán)。
上面就是注釋中所說的東西,簡單的來講,這個函數(shù)就是用來判斷當前frame
是否接受在布局過程layout pass
中View的重新布局請求。而且這個函數(shù)的代碼邏輯也很簡單,首先傳進來的view
參數(shù)就是請求重新布局的View對象,第一個條件判斷(view.mParent == null || view.mAttachInfo == null)
表示view沒有父布局或view沒有所屬的窗口,簡單來說就是該View不會觸發(fā)其他的布局。所以接受布局請求返回true
。接著就將view不重復地添加到mLayoutRequesters
集合中,然后根據(jù)mHandlingLayoutInLayoutRequest
來判斷當前是否正在進行布局請求的處理。若正在進行布局請求的處理就返回false
,將當前的view的布局請求放到下一次frame
進行,否則就接受請求,并在第二次布局過程中處理。
理解了這個函數(shù)后在回過來看我們的performLayout()
的代碼就簡單不少了。但我發(fā)現(xiàn)performLayout()
的代碼里還有一個比較關鍵的函數(shù)getValidLayoutRequesters()
。這個函數(shù)看它的名字就知道是為了得到前面所說的有布局請求的View的集合。但我們前面講了將在布局過程layout pass
中發(fā)生的requestLayout()
分成了兩種情況,那它又是怎樣進行處理的呢?我們可以先到getValidLayoutRequesters()
的代碼中先看看。
/**
* This method is called during layout when there have been calls to requestLayout() during
* layout. It walks through the list of views that requested layout to determine which ones
* still need it, based on visibility in the hierarchy and whether they have already been
* handled (as is usually the case with ListView children).
*
* @param layoutRequesters The list of views that requested layout during layout
* @param secondLayoutRequests Whether the requests were issued during the second layout pass.
* If so, the FORCE_LAYOUT flag was not set on requesters.
* @return A list of the actual views that still need to be laid out.
*/
private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
boolean secondLayoutRequests) {
int numViewsRequestingLayout = layoutRequesters.size();
// 用于暫存有布局請求的view
ArrayList<View> validLayoutRequesters = null;
// 遍歷每個需要請求布局的view
for (int i = 0; i < numViewsRequestingLayout; ++i) {
View view = layoutRequesters.get(i);
// 判斷是否需要檢查和清除view的flag
if (view != null && view.mAttachInfo != null && view.mParent != null &&
(secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
View.PFLAG_FORCE_LAYOUT)) {
boolean gone = false;
View parent = view;
// Only trigger new requests for views in a non-GONE hierarchy
// 只觸發(fā)`view tree`層級結(jié)構(gòu)中可見view的布局請求
while (parent != null) {
if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
gone = true;
break;
}
if (parent.mParent instanceof View) {
parent = (View) parent.mParent;
} else {
parent = null;
}
}
if (!gone) {
if (validLayoutRequesters == null) {
validLayoutRequesters = new ArrayList<View>();
}
// 將需要處理的view加入到返回集合
validLayoutRequesters.add(view);
}
}
}
// 判斷是否為第2次獲取view集合
if (!secondLayoutRequests) {
// If we're checking the layout flags, then we need to clean them up also
// 遍歷集合里所有的view,并將其中的flag重置
for (int i = 0; i < numViewsRequestingLayout; ++i) {
View view = layoutRequesters.get(i);
while (view != null &&
(view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
if (view.mParent instanceof View) {
view = (View) view.mParent;
} else {
view = null;
}
}
}
}
// 清除參數(shù)集合中的所有view,給下一次`frame`提供一個空的容器
layoutRequesters.clear();
return validLayoutRequesters;
}
這個函數(shù)的功能就是遍歷參數(shù)layoutRequesters
里的每個View,根據(jù)每個View在view tree
層級結(jié)構(gòu)中是否可見以及是否已被處理,來判斷哪些view仍然需要布局。其中第2個參數(shù)secondLayoutRequests
表示獲取的view集合是否是在一次frame
里的第2次獲取。因為我們需要將布局請求分為兩種,在第2次獲取view的集合時得到的是在下一次frame
中需要進行處理的view,而代碼中是通過第2個參數(shù)secondLayoutRequests
來區(qū)分。
簡單的來說,這個函數(shù)就是用來獲取當前frame
中需要進行布局處理的view集合或者獲取下一次frame
時需要進行布局處理的view集合。
知道了mLayoutRequesters
這個成員屬性和getValidLayoutRequesters()
函數(shù)的意義后我們可以來繼續(xù)分析我們的performLayout()
的代碼了。
// 獲取當前請求布局的View的數(shù)量
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// 獲取當前`frame`需要進行處理布局請求的View的集合
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// 表示當前正在處理布局請求
mHandlingLayoutInLayoutRequest = true;
int numValidRequests = validLayoutRequesters.size();
// 對集合中的每個view并對它進行新的布局請求,進行測量和布局
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
// 對整個View樹進行重新測量
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
// 表示當前正在布局
mInLayout = true;
// 進行第2次布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 表示布局請求處理完畢
mHandlingLayoutInLayoutRequest = false;
// 獲取下次`frame`需要進行布局處理的view集合
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// 將相應布局請求發(fā)生到下一次`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);
.........
view.requestLayout();
}
}
});
}
}
}
..........
// 表示布局結(jié)束
mInLayout = false;
}
我在代碼中已經(jīng)做了詳細的注釋,我相信通過對mLayoutRequesters
和getValidLayoutRequesters()
兩個關鍵點的講解后,大家都能輕易的理解performLayout()
的基本邏輯。那么現(xiàn)在我們要進入到View真正進行布局的地方---layout()
.
揭開layout()
的面目
layout(int l, int t, int r, int b)
這是我們的函數(shù)原型,4個參數(shù)分別是由父布局傳進來的,代表view在父布局中所放置的位置信息,分別為距離父布局的上下左右的位置。下面給出示意圖。
知道了參數(shù)的意義后,話不多說,馬上讓我們的主角登場吧!
/**
* Assign a size and position to a view and all of its
* descendants
*
* <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>
*
* @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
*/
@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;
// 判斷當前的View的顯示模式的并進行邊界的設置,并以此來判斷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);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
// 執(zhí)行布局的監(jiān)聽接口,像onClickListener等監(jiān)聽接口一樣
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);
}
}
}
// 設置相應的flag
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
我先來解析一下文檔注釋的內(nèi)容吧。首先這個函數(shù)是給View和它的后代節(jié)點(在view tree
上)分配一個大小和位置,將它們布局在窗口上。這是布局機制的第2個階段(第1階段是測量階段)。在這個階段view tree
上的
每個父階段都調(diào)用孩子節(jié)點的layout()
函數(shù)來放置它們,而且使用的是孩子節(jié)點在測量過程measure pass
中保存的測量值。View的派生類不應該重寫這個函數(shù)來實現(xiàn)自定義布局,而應該重寫onLayout()
函數(shù),并在onLayout()
中對它的孩子進行布局。
好了,解析完文檔的內(nèi)容后我們來分析代碼吧。代碼的邏輯也并不復雜,首先通過View的flag
判斷在布局前是否需要進行測量。然后根據(jù)window的模式來對View進行布局。
下面我們來看看onLayout()
這個函數(shù)吧:
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @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) {
}
可以看到這個函數(shù)在View
類下的實現(xiàn)是空的函數(shù)體。這就說明了我們需要在子類來重寫onLayout()
函數(shù)來完成我們的自定義布局。通常情況下,ViewGroup
是需要實現(xiàn)該函數(shù)的,因為它需要處理子View的布局。既然這樣,我們就來看一下Android提供的FrameLayout
類所實現(xiàn)的onLayout()
的代碼:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 對子View進行布局
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
// 得到子View的數(shù)量
final int count = getChildCount();
// 計算得到FrameLayout的上下左右
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
// 遍歷每一個子View,按照FrameLayout的屬性對子View進行布局
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 只對屬性不為GONE的view進行布局
if (child.getVisibility() != GONE) {
// 得到子View的布局參數(shù)
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 得到子View測量后的寬高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
// 獲取View的gravity屬性
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
// 得到FrameLayout的布局方向,RTL或LTR
final int layoutDirection = getLayoutDirection();
// 根據(jù)子View的gravity屬性和FrameLayout的布局方向
// 設置布局開始和結(jié)束的位置,是從左邊開始,到右邊結(jié)束;還是從右邊開始,到左邊結(jié)束。
// 得到一個代表水平布局方向的值
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
// 下面的兩個switch語句分別根據(jù)布局的垂直方向?qū)傩院退椒较驅(qū)傩? // 計算子View的上左位置,即Top和Left(將邊距考慮在內(nèi))
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進行布局
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
雖然代碼有點長,但邏輯都非常的清晰,而且我在代碼中也做了相應的注釋,相信大家能很容易的就看得明白。
requestLayout()
與invalidate()
到這里我們的布局過程layout pass
也分析完畢了。但這里還有一個疑問,就是我們在分析performLayout()
的時候多次提到了requestLayout()
這個函數(shù),但卻不清楚它的內(nèi)容,既然這樣,我們就來看看requestLayout()
的真面目吧。
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// 只有在當前的View請求布局,而不是父級層級的View請求布局時
// 才觸發(fā)布局時請求的邏輯代碼
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
// 設置相應的布局flag,或操作表示在flag中添加該標志
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 判斷是否需要對父View進行布局請求
if (mParent != null && !mParent.isLayoutRequested()) {
// 調(diào)用ViewRootImpl的`requestLayout()`
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
照樣的,我先講講文檔注釋中的內(nèi)容。當我們的View的內(nèi)容發(fā)生了改變導致View的布局變得無效(即我們想要的效果沒有正確的展現(xiàn)在布局上),這個時候我們可以調(diào)用requestLayout()
來告訴系統(tǒng)讓系統(tǒng)在view tree
上執(zhí)行一次layout pass
來刷新布局。如果正在執(zhí)行布局layout pass
的時候調(diào)用了requestLayout()
,那么布局的請求可能會在當前布局結(jié)束的時候重新進行一次layout pass
,或者是等到下一次frame
的時候才進行布局。如果子類要重寫該方法,需要先調(diào)用super.requestLayout()
以保證能正確的處理各種布局請求。
文檔所說的與我們在前面分析的requestLayoutDuringLayout()
函數(shù)時是一樣的。如果前面的分析看懂了,那么結(jié)合前面的分析,requestLayout()
的代碼也很容易就能明白。
這里有一點需要注意的,就是下面部分的代碼:
// 判斷是否需要對父View進行布局請求
if (mParent != null && !mParent.isLayoutRequested()) {
// 調(diào)用ViewRootImpl的`requestLayout()`
mParent.requestLayout();
}
這里根據(jù)條件,調(diào)用了ViewRootImpl
的requestLayout()
函數(shù),這個函數(shù)就是我在前面的文章中提到過的,調(diào)用了scheduleTraversals()
的函數(shù)。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
而且我們也知道了這個函數(shù)最后會調(diào)用ViewRootImpl
的performTraversal()
函數(shù),會執(zhí)行一遍View的measure,layout,draw
流程。
這就說明了,當View的requestLayout()
被調(diào)用的時候,我們的整個view tree
可能會進行一次measure,layout,draw
的過程,這時我們的ViewRootImpl
中的performMeasure()
和performLayout()
會一定被調(diào)用,但performDraw()
就有可能被調(diào)用也有可能不被調(diào)用。
既然講到了requestLayout()
,那就不得不提與它非常密切的invalidate()
了。
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
}
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
// 將View中指定的區(qū)域變?yōu)闊o效
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
// 去掉flag中的draw標記,以此來表示view未被繪制
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
// 調(diào)用`ViewRootImpl`的`invalidateChild()`
p.invalidateChild(this, damage);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
// Damage the entire IsolatedZVolume receiving this view's shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
}
注釋說了,這個函數(shù)會可見的View變成無效的狀態(tài),即需要進行重新繪制,這就說明了onDraw()
函數(shù)將會被調(diào)用。
上面的代碼我們關注的部分是,首先invalidate()
函數(shù)最終調(diào)用的是invalidateInternal()
函數(shù)。所以我們直接看這個函數(shù)。在invalidateInternal()
里,通過mPrivateFlags &= ~PFLAG_DRAWN;
這句代碼將flag
相應的位設置為未被繪制。然后可以看到通過p.invalidateChild(this, damage);
調(diào)用了ViewRootImpl
的invalidateChild()
。下面就是ViewRootImpl
中相關的代碼。
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
// 關注這里
scheduleTraversals();
}
}
我們只需關注上面最后的那句代碼scheduleTraversals();
即可。這里也調(diào)用了scheduleTraversals()
,也就是說最終也會調(diào)用performTraversals()
函數(shù),但是由于沒有添加measure
和layout
的標記到flag中,所以只會調(diào)用performDraw()
函數(shù)。
總的來說:
-
invalidate()
函數(shù)會觸發(fā)performDraw()
過程。 -
requestLayout()
就會觸發(fā)performMeasure()
和performLayout()
過程,也有可肯觸發(fā)performDraw()
。
注意:
invalidate()
需要在UI線程里被調(diào)用,如果要在非UI線程里調(diào)用,就需要調(diào)用postInvalidate()
。
更多關于invalidate()
和requestLayout()
的信息可以參考這兩篇博文:
總結(jié)
最后還是用圖片進行總結(jié)吧,因為這樣最實際。