View和ViewGroup中的mParent
源碼版本為 Android 10(Api 29)空凸,不同Android版本可能有一些差別
mParent
從名字看,應(yīng)該表示父View,而這篇博客我們就是要探索View
和ViewGroup
中的mParent
分別表示什么竭贩,以及在什么地方賦值的旧困。
頁(yè)面頂層View(DecorView)中 mParent 表示的什么?
通過(guò)《Activity 的組成》 我們知道了界面的頂層View
是DecorView
循捺,那么我們查看mParent
的來(lái)源淹冰,首先就應(yīng)該查看 DecorView
的mParent
是什么,在哪里被賦值的巨柒。
通過(guò)《Activity常見(jiàn)問(wèn)題》的 【Activity 在 onResume 之后才顯示的原因是什么樱拴?】 部分能知道DecorView
是怎樣添加到PhoneWindow
的(ActivityThread#handleResumeActivity()
-> WindowManagerImpl#addView()
-> WindowManagerGlobal#addView()
-> ViewRootImpl#setView()
)。在 ViewRootImpl#setView()
方法中通過(guò)調(diào)用 Session#addToDisplay()
方法開(kāi)始實(shí)現(xiàn)客戶端與WMS之間的綁定洋满,不過(guò)這里不是我們今天關(guān)注的重點(diǎn)晶乔。我們今天關(guān)注的重點(diǎn)在這個(gè)方法的另一行那個(gè)代碼。在來(lái)看一下ViewRootImpl#setView()
方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 調(diào)用 requestLayout() 方法牺勾,進(jìn)行布局(包括measue正罢、layout、draw)
requestLayout();
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 通過(guò)調(diào)用 Session 的 addToDisplay() 方法
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
// 調(diào)用 View 的 assignParent() 方法
view.assignParent(this);
}
}
}
這里我們最關(guān)心的就是 view.assignParent(this)
代碼驻民,傳遞了 this
對(duì)象(this
就是ViewRootImpl
)翻具,這里的View
就是DecorView
,這個(gè)方法在View
中實(shí)現(xiàn)回还。我們來(lái)看一下 View#assignParent(this)
方法:
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but it already has a parent");
}
}
接收一個(gè) ViewParent
參數(shù)裆泳,ViewRootImpl
是實(shí)現(xiàn)了這個(gè)接口的,而且這里傳遞的也是它柠硕,所以到了這里我們就知道了:頂層 DecorView
中的 mParent
參數(shù)表示的就是 ViewRootImpl
工禾,知道了頂層DecorView
中的 mParent
表示的是什么,那么我么就繼續(xù)看看DecorView
的子View
中的mParent
表示的什么吧蝗柔?
非DecorView中 mParent 表示的什么闻葵,在什么時(shí)候賦值的?
首先我們查看ViewGroup
類的聲明:
public abstract class ViewGroup extends View implements ViewParent, ViewManager
發(fā)現(xiàn)ViewGroup
也實(shí)現(xiàn)了ViewParent
接口癣丧。
我們?cè)谏厦嬲f(shuō)知道了View#assignParent()
方法的參數(shù)是需要一個(gè) ViewParent
對(duì)象槽畔,而剛好ViewGroup
也實(shí)現(xiàn)了ViewParent
接口,DecorView
也是間接繼承ViewGroup
胁编,那么子View
的mParent
是不是就是DecorView
呢厢钧?我們接著往下看。
在我們平常寫代碼中掏呼,獲取父控件時(shí)通常是這樣寫的代碼:
ViewGroup viewGroup = (ViewGroup) view.getParent();
直接通過(guò)view.getParent()
然后強(qiáng)轉(zhuǎn)成 ViewGroup
坏快,而View#getParent()
方法返回的正是 View
的 mParent
成員變量,那么我們也可以在一定程度上認(rèn)為 View
的 mParent
就是 ViewGroup
憎夷。
在上面說(shuō)了莽鸿,給mParent
賦值地方是在View#assignParent()
方法,那么我們就可以反過(guò)來(lái)看,在DecorView
中哪里調(diào)用了這個(gè)方法祥得,結(jié)果沒(méi)有發(fā)現(xiàn)有調(diào)用這個(gè)方法兔沃,繼續(xù)查看 FrameLayout
類,發(fā)現(xiàn)也沒(méi)有調(diào)用過(guò)這個(gè)方法级及,接著看ViewGroup
類乒疏,發(fā)現(xiàn)了在ViewGroup
中的 addViewInner()
方法中有調(diào)用這個(gè)方法。
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
}
接著查找 addViewInner()
方法在哪里被調(diào)用了饮焦,我們發(fā)現(xiàn)在ViewGroup
中的addView()
方法:
public void addView(View child, int index, LayoutParams params) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// 請(qǐng)求重新測(cè)量怕吴、布局和繪制
requestLayout();
invalidate(true);
// 調(diào)用 addViewInner() 方法
addViewInner(child, index, params, false);
}
上面說(shuō)到了,每一個(gè)View
添加到ViewGroup
中都是通過(guò)addView()
方法來(lái)實(shí)現(xiàn)的县踢,現(xiàn)在我們找到了在addView()
方法中調(diào)用了addViewInner()
方法转绷,然后調(diào)用了子View
的 assignParent()
方法,并且將自身作為參數(shù)傳遞了硼啤。而assignParent()
方法中就是對(duì)mParent
進(jìn)行賦值议经。那么到這里我們就知道了:子View
的 mParent
是父View
在調(diào)用addView()
方法將子View
添加到自身時(shí),通過(guò)調(diào)用子View
的 assignParent()
方法將自身傳遞給子View
作為子View
的mParent
成員變量值谴返。也就是非DecorView
的 mParent
成員變量表示的就是 ViewGroup
煞肾。
擴(kuò)展
ViewGroup
中的 addView()
、addViewInLayout()
和 attachViewToParent()
方法比較
先了解幾點(diǎn):
- 在
ViewGroup
中有一個(gè)View
數(shù)組mChildren
保存了該ViewGroup
中的所有子View
- 對(duì)
mChildren
的操作(增加嗓袱、刪除籍救、插入)在ViewGroup#addInArray()
方法中
幾個(gè)方法對(duì)比
addView()
:會(huì)調(diào)用requestLayout()
和invalidate(true)
方法強(qiáng)制調(diào)用當(dāng)前控件的測(cè)量、布局和繪制方法索抓,然后調(diào)用ViewGroup#addViewInner()
方法钧忽,在addViewInner()
方法中會(huì)給mParent
賦值并且回調(diào)用子控件的requestLayout()
方法,同時(shí)調(diào)用addInArray()
方法修改View
數(shù)組addViewInLayout()
:不會(huì)調(diào)用requestLayout()
和invalidate(true)
方法逼肯;直接通過(guò)child.mParent = null;
將View
的mParent
置為null
,然后調(diào)用ViewGroup#addViewInner()
方法桃煎,在addViewInner()
方法中會(huì)給mParent
賦值并且回調(diào)用子控件的requestLayout()
方法篮幢,同時(shí)調(diào)用addInArray()
方法修改View
數(shù)組attachViewToParent()
:不會(huì)調(diào)用requestLayout()
和invalidate(true)
方法,同時(shí)也不會(huì)調(diào)用ViewGroup#addViewInner()
方法为迈;而是直接調(diào)用addInArray()
方法修改View
數(shù)組三椿,然后直接給View
的mParent
賦值child.mParent = this;
addViewInLayout()
和attachViewToParent()
方法一般在子View
可復(fù)用的ViewGroup
中調(diào)用,如ViewPager
葫辐、ListView
搜锰、GridView
、RecyclerView
等耿战。