深入到ViewGroup內(nèi)部术辐,了解ViewGroup的工作,同時(shí)會(huì)闡述更多有關(guān)于View的相關(guān)知識(shí)。以便為以后能靈活的使用自定義空間打更近一步的基礎(chǔ)署隘。
ViewGroup定義
一個(gè)ViewGroup是一個(gè)可以包含子View的容器蘑秽,是布局文件和View容器的基類饺著。在這個(gè)類里定義了ViewGroup.LayoutParams類,這個(gè)類是布局參數(shù)的子類肠牲。
其實(shí)ViewGroup也就是View的容器幼衰。通過(guò)ViewGroup.LayoutParams來(lái)指定子View的參數(shù)。
ViewGroup作為一個(gè)容器缀雳,為了制定這個(gè)容器應(yīng)有的標(biāo)準(zhǔn)所以為其指定了接口
public abstract class ViewGroup extends View implements ViewParent, ViewManager
這兩個(gè)接口這里不研究渡嚣,如果涉及到的話會(huì)帶一下。ViewGroup有小4000行代碼肥印,下面我們一個(gè)模塊一個(gè)模塊分析识椰。
ViewGroup容器
ViewGroup是一個(gè)容器,其采用一個(gè)數(shù)組來(lái)存儲(chǔ)這些子View:
// Child views of this ViewGroup
private View[] mChildren;
由于是通過(guò)一個(gè)數(shù)組來(lái)存儲(chǔ)View數(shù)據(jù)的深碱,所以對(duì)于ViewGroup來(lái)說(shuō)其必須實(shí)現(xiàn)增腹鹉、刪、查的算法敷硅。下面我們就來(lái)看看其內(nèi)部實(shí)現(xiàn)功咒。
1. 添加View的算法
protected boolean addViewInLayout(View child, int index, LayoutParams params) {
return addViewInLayout(child, index, params, false);
}
protected boolean addViewInLayout(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
child.mParent = null;
addViewInner(child, index, params, preventRequestLayout);
child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
return true;
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
addInArray(child, index);
...
}
private void addInArray(View child, int index) {
...
}
上面四個(gè)方法就是添加View的核心算法的封裝,它們是層層調(diào)用的關(guān)系竞膳。而我們通常調(diào)用的addView就是最終通過(guò)上面那個(gè)來(lái)最終達(dá)到添加到ViewGroup中的航瞭。
1.1 addViewInner方法:
1. 首先是對(duì)子View是否已經(jīng)包含到一個(gè)父容器中,主要的防止添加一個(gè)已經(jīng)有父容器的View坦辟,因?yàn)樘砑右粋€(gè)擁有父容器的View時(shí)會(huì)碰到各種問(wèn)題刊侯。比如記錄本身父容器算法的問(wèn)題、本身被多個(gè)父容器包含時(shí)更新的處理等等一系列的問(wèn)題都會(huì)出現(xiàn)锉走。
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
2. 然后就是對(duì)子View布局參數(shù)的處理
3. 調(diào)用addInArray來(lái)添加View
4. 父View為當(dāng)前的ViewGroup
5. 焦點(diǎn)的處理
6. 當(dāng)前View的AttachInfo信息滨彻,這個(gè)信息是用來(lái)在窗口處理中用的。Android的窗口系統(tǒng)就是用過(guò)AttachInfo來(lái)判斷View的所屬窗口的挪蹭,這個(gè)了解下就行亭饵。詳細(xì)信息設(shè)計(jì)到Android框架層的一些東西。
AttachInfo ai = mAttachInfo;
if (ai != null) {
boolean lastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
if (ai.mKeepScreenOn) {
needGlobalAttributesUpdate(true);
}
ai.mKeepScreenOn = lastKeepOn;
}
7. View樹(shù)改變的監(jiān)聽(tīng)
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(this, child);
}
8. 子View中的mViewFlags的設(shè)置
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
}
1.2 addInArray方法:
這個(gè)里面的實(shí)現(xiàn)主要是有個(gè)知識(shí)點(diǎn)梁厉,以前也沒(méi)用過(guò)arraycopy辜羊,這里具體實(shí)現(xiàn)就不多加描述了踏兜。
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
2. 移除View
移除View的幾種方式:
移除指定的View
移除從指定位置的View
移除從指定位置開(kāi)始的多個(gè)View
移除所有的View
其中具體涉及到的方法就有好多了,不過(guò)最終對(duì)要?jiǎng)h除的子View中所做的無(wú)非就是下列的事情:
如果擁有焦點(diǎn)則清除焦點(diǎn)
將要?jiǎng)h除的View從當(dāng)前的window中解除關(guān)系
設(shè)置View樹(shù)改變的事件監(jiān)聽(tīng)八秃,我們可以通過(guò)監(jiān)聽(tīng)OnHierarchyChangeListener事件來(lái)進(jìn)行一些相應(yīng)的處理
從父容器的子容器數(shù)組中刪除
具體的內(nèi)容這里就不一一貼出來(lái)了碱妆,大家回頭看看源碼就可以了。
3. 查詢
這個(gè)就簡(jiǎn)單了昔驱,就是直接從數(shù)組中取出就可以了:
public View getChildAt(int index) {
try {
return mChildren[index];
} catch (IndexOutOfBoundsException ex) {
return null;
}
}
分析到這兒疹尾,其實(shí)我們已經(jīng)相當(dāng)于分析了ViewGroup四分之一的代碼了,@_@骤肛。
onFinishInflate
我們一般使用View的流程是在onCreate中使用setContentView來(lái)設(shè)置要顯示Layout文件或直接創(chuàng)建一個(gè)View纳本,在當(dāng)設(shè)置了ContentView之后系統(tǒng)會(huì)對(duì)這個(gè)View進(jìn)行解析,然后回調(diào)當(dāng)前視圖View中的onFinishInflate方法腋颠。只有解析了這個(gè)View我們才能在這個(gè)View容器中獲取到擁有Id的組件繁成,同樣因?yàn)橄到y(tǒng)解析完View之后才會(huì)調(diào)用onFinishInflate方法,所以我們自定義組件時(shí)可以onFinishInflate方法中獲取指定子View的引用秕豫。
測(cè)量組件
在ViewGroup中提供了測(cè)量子組件的三個(gè)方法朴艰。
__1观蓄、measureChild(View, int, int)混移,為子組件添加Padding __
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
__2、measureChildren(int, int)根據(jù)指定的高和寬來(lái)測(cè)量所有子View中顯示參數(shù)非GONE的組件侮穿。 __
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
3歌径、measureChildWithMargins(View, int, int, int, int)測(cè)量指定的子組件,為子組件添加Padding和Margin亲茅。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面三個(gè)方法都是為子組件設(shè)置了布局參數(shù)回铛。最終調(diào)用的方法是子組件的measure方法。在View中我們知道這個(gè)調(diào)用實(shí)際上就是設(shè)置了子組件的布局參數(shù)并且調(diào)用onMeasure方法克锣,最終設(shè)置了View測(cè)量后的高度和寬度茵肃。
onLayout
這個(gè)函數(shù)是一個(gè)抽象函數(shù),要求實(shí)現(xiàn)ViewGroup的函數(shù)必須實(shí)現(xiàn)這個(gè)函數(shù)袭祟,這也就是ViewGroup是一個(gè)抽象函數(shù)的原因验残。因?yàn)楦鞣N組件實(shí)現(xiàn)的布局方式不一樣,而onLayout是必須被重載的函數(shù)碳蛋。
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
我們回頭來(lái)看View中l(wèi)ayout方法:
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
在這個(gè)方法中調(diào)用了setFrame方法士袄,這個(gè)方法是用來(lái)設(shè)置View中的上下左右邊距用的
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
//.......
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & DRAWN;
// Invalidate our old position
invalidate();
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mPrivateFlags |= HAS_BOUNDS;
int newWidth = right - left;
int newHeight = bottom - top;
if (newWidth != oldWidth || newHeight != oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, therby clearing
// the DRAWN bit.
mPrivateFlags |= DRAWN;
invalidate();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
}
return changed;
}
//我們可以看到如果新的高度和寬度改變之后會(huì)調(diào)用重新設(shè)置View的四個(gè)參數(shù):
//protected int mLeft;
//protected int mRight;
//protected int mTop;
//protected int mBottom;
//這四個(gè)參數(shù)指定了View將要布局的位置她肯。而繪制的時(shí)候是通過(guò)這四個(gè)參數(shù)來(lái)繪制,所以我們?cè)赩iew中調(diào)用layout方法可以實(shí)現(xiàn)指定子View中布局氨鹏。
ViewGroup的繪制
ViewGroup的繪制實(shí)際上是調(diào)用的dispatchDraw,繪制時(shí)需要考慮動(dòng)畫問(wèn)題压状,而動(dòng)畫的實(shí)現(xiàn)實(shí)際上就通過(guò)dispatchDraw來(lái)實(shí)現(xiàn)的仆抵。
我們不用理會(huì)太多的細(xì)節(jié),直接看其繪制子組件調(diào)用的是drawChild方法,這個(gè)里面具體的東西就多了镣丑,涉及到動(dòng)畫效果的處理还栓,如果有機(jī)會(huì)的話再寫,我們只要知道這個(gè)方法的功能就行传轰。
這里有個(gè)demo貼出其中的代碼大家可以測(cè)試下剩盒。
public ViewGroup01(Context context)
{
super(context);
Button mButton = new Button(context);
mButton.setText("測(cè)試");
addView(mButton);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
View v = getChildAt(0);
if(v != null)
{
v.layout(120, 120, 250, 250);
}
}
@Override
protected void dispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
View v = getChildAt(0);
if(v != null)
{
drawChild(canvas, v, getDrawingTime());
}
}