一個ViewGroup是一個能夠包含子View的容器,是佈局文件和View容器的基類。在這個類裏定義了ViewGroup.LayoutParams類,這個類是佈局參數的子類。算法
其實ViewGroup也就是View的容器。經過ViewGroup.LayoutParams來指定子View的參數。canvas
ViewGroup做爲一個容器,爲了制定這個容器應有的標準因此爲其指定了接口數組
public abstract class ViewGroup extends View implements ViewParent, ViewManager 框架
這兩個接口這裏不研究,若是涉及到的話會帶一下。ViewGroup有小4000行代碼,下面咱們一個模塊一個模塊分析。ide
ViewGroup是一個容器,其採用一個數組來存儲這些子View:函數
// Child views of this ViewGroup 佈局
private View[] mChildren; 測試
因爲是經過一個數組來存儲View數據的,因此對於ViewGroup來講其必須實現增、刪、查的算法。下面咱們就來看看其內部實現。動畫
protected boolean addViewInLayout(View child, int index, LayoutParams params) { this
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) {
...
}
上面四個方法就是添加View的核心算法的封裝,它們是層層調用的關係。而咱們一般調用的addView就是最終經過上面那個來最終達到添加到ViewGroup中的。
首先是對子View是否已經包含到一個父容器中,主要的防止添加一個已經有父容器的View,由於添加一個擁有父容器的View時會碰到各類問題。好比記錄自己父容器算法的問題、自己被多個父容器包含時更新的處理等等一系列的問題都會出現。
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
而後就是對子View佈局參數的處理。
調用addInArray來添加View
父View爲當前的ViewGroup
焦點的處理。
當前View的AttachInfo信息,這個信息是用來在窗口處理中用的。Android的窗口系統就是用過AttachInfo來判斷View的所屬窗口的,這個瞭解下就行。詳細信息設計到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;
}
View樹改變的監聽
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(this, child);
}
子View中的mViewFlags的設置:
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
}
這個裏面的實現主要是有個知識點,之前也沒用過arraycopy,這裏具體實現就很少加描述了。
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
移除View的幾種方式:
移除指定的View。
移除從指定位置的View
移除從指定位置開始的多個View
移除全部的View
其中具體涉及到的方法就有好多了,不過最終對要刪除的子View中所作的無非就是下列的事情:
若是擁有焦點則清楚焦點
將要刪除的View從當前的window中解除關係。
設置View樹改變的事件監聽,咱們能夠經過監聽OnHierarchyChangeListener事件來進行一些相應的處理。
從父容器的子容器數組中刪除。
具體的內容這裏就不一一貼出來了,你們回頭看看源碼就哦了。
這個就簡單了,就是直接從數組中取出就能夠了:
public View getChildAt(int index) {
try {
return mChildren[index];
} catch (IndexOutOfBoundsException ex) {
return null;
}
}
分析到這兒,其實咱們已經至關於分析了ViewGroup四分之一的代碼了,呵呵。
咱們通常使用View的流程是在onCreate中使用setContentView來設置要顯示Layout文件或直接建立一個View,在當設置了ContentView以後系統會對這個View進行解析,而後回調當前視圖View中的onFinishInflate方法。只有解析了這個View咱們才能在這個View容器中獲取到擁有Id的組件,一樣由於系統解析完View以後纔會調用onFinishInflate方法,因此咱們自定義組件時能夠onFinishInflate方法中獲取指定子View的引用。
在ViewGroup中提供了測量子組件的三個方法。
//一、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);
}
//二、measureChildren(int, int)根據指定的高和寬來測量全部子View中顯示參數非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)測量指定的子組件,爲子組件添加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);
}
上面三個方法都是爲子組件設置了佈局參數。最終調用的方法是子組件的measure方法。在View中咱們知道這個調用實際上就是設置了子組件的佈局參數而且調用onMeasure方法,最終設置了View測量後的高度和寬度。
這個函數是一個抽象函數,要求實現ViewGroup的函數必須實現這個函數,這也就是ViewGroup是一個抽象函數的緣由。由於各類組件實現的佈局方式不同,而onLayout是必須被重載的函數。
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
來看View中layout方法:
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;
}
在這個方法中調用了setFrame方法,這個方法是用來設置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;
}
//咱們能夠看到若是新的高度和寬度改變以後會調用從新設置View的四個參數:
//protected int mLeft;
//protected int mRight;
//protected int mTop;
//protected int mBottom;
//這四個參數指定了View將要佈局的位置。而繪製的時候是經過這四個參數來繪製,因此咱們在View中調用layout方法能夠實現指定子View中佈局。
ViewGroup的繪製其實是調用的dispatchDraw,繪製時須要考慮動畫問題,而動畫的實現實際上就經過dispatchDraw來實現的。
咱們不用理會太多的細節,直接看其繪製子組件調用的是drawChild方法,這個裏面具體的東西就多了,涉及到動畫效果的處理,若是有機會的話再寫,咱們只要知道這個方法的功能就行。
這裏有個demo貼出其中的代碼你們能夠測試下。
public ViewGroup01(Context context)
{
super(context);
Button mButton = new Button(context);
mButton.setText("測試");
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());
}
}