【 tulaoshi.com - 编程语言 】
                             
                            组合模式定义:
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

如上图所示(截取自《Head First Design Patterns》一书),主要包括三个部分:
1. Component抽象组件。定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。
(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)2. Leaf叶子节点。构成组合树的最小构建单元。
(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)3. Composite树枝节点组件。它的作用是组合树枝节点和叶子节点形成一个树形结构。
高层模块调用简单。一棵树形结构的所有节点都是Component,局部和整体对调用者来说都是一样的,没有区别,所以高层模块不比关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
节点自由扩展增加。使用组合模式,如果想增加一个树枝节点或者叶子节点都是很简单的,只要找到它的父节点就可以了,非常容易扩展,符合“开闭原则”。
应用最广的模式之一。应用在维护和展示部分-整体关系的场景,如树形菜单、文件夹管理等等。
在Android源码中,都能找到使用组合模式的例子,其中在《Android源码学习之观察者模式应用》介绍到的ViewGroup和View的结构就是一个组合模式,结构图如下所示:

现在来看看它们是如何利用组合模式组织在一起的,首先在View类定义了有关具体操作,然后在ViewGroup类中继承View类,并添加相关的增加、删除和查找孩子View节点,代码如下:
代码如下:
* @attr ref android.R.styleable#ViewGroup_clipChildren 
* @attr ref android.R.styleable#ViewGroup_clipToPadding 
* @attr ref android.R.styleable#ViewGroup_layoutAnimation 
* @attr ref android.R.styleable#ViewGroup_animationCache 
* @attr ref android.R.styleable#ViewGroup_persistentDrawingCache 
* @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache 
* @attr ref android.R.styleable#ViewGroup_addStatesFromChildren 
* @attr ref android.R.styleable#ViewGroup_descendantFocusability 
* @attr ref android.R.styleable#ViewGroup_animateLayoutChanges 
*/ 
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
接着看增加孩子节点函数:
代码如下:
/** 
* Adds a child view. If no layout parameters are already set on the child, the 
* default parameters for this ViewGroup are set on the child. 
* 
* @param child the child view to add 
* 
* @see #generateDefaultLayoutParams() 
*/ 
public void addView(View child) { 
addView(child, -1); 
} 
/** 
* Adds a child view. If no layout parameters are already set on the child, the 
* default parameters for this ViewGroup are set on the child. 
* 
* @param child the child view to add 
* @param index the position at which to add the child 
* 
* @see #generateDefaultLayoutParams() 
*/ 
public void addView(View child, int index) { 
LayoutParams params = child.getLayoutParams(); 
if (params == null) { 
params = generateDefaultLayoutParams(); 
if (params == null) { 
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); 
} 
} 
addView(child, index, params); 
} 
/** 
* Adds a child view with this ViewGroup's default layout parameters and the 
* specified width and height. 
* 
* @param child the child view to add 
*/ 
public void addView(View child, int width, int height) { 
final LayoutParams params = generateDefaultLayoutParams(); 
params.width = width; 
params.height = height; 
addView(child, -1, params); 
} 
/** 
* Adds a child view with the specified layout parameters. 
* 
* @param child the child view to add 
* @param params the layout parameters to set on the child 
*/ 
public void addView(View child, LayoutParams params) { 
addView(child, -1, params); 
} 
/** 
* Adds a child view with the specified layout parameters. 
* 
* @param child the child view to add 
* @param index the position at which to add the child 
* @param params the layout parameters to set on the child 
*/ 
public void addView(View child, int index, LayoutParams params) { 
if (DBG) { 
System.out.println(this + " addView"); 
} 
// addViewInner() will call child.requestLayout() when setting the new LayoutParams 
// therefore, we call requestLayout() on ourselves before, so that the child's request 
// will be blocked at our level 
requestLayout(); 
invalidate(true); 
addViewInner(child, index, params, false); 
}
 
在ViewGroup中我们找到了添加addView()方法,有了增加孩子节点,肯定有相对应删除孩子节点的方法,接着看:
代码如下: 
public void removeView(View view) { 
removeViewInternal(view); 
requestLayout(); 
invalidate(true); 
} 
/** 
* Removes a view during layout. This is useful if in your onLayout() method, 
* you need to remove more views. 
* 
* @param view the view to remove from the group 
*/ 
public void removeViewInLayout(View view) { 
removeViewInternal(view); 
} 
/** 
* Removes a range of views during layout. This is useful if in your onLayout() method, 
* you need to remove more views. 
* 
* @param start the index of the first view to remove from the group 
* @param count the number of views to remove from the group 
*/ 
public void removeViewsInLayout(int start, int count) { 
removeViewsInternal(start, count); 
} 
/** 
* Removes the view at the specified position in the group. 
* 
* @param index the position in the group of the view to remove 
*/ 
public void removeViewAt(int index) { 
removeViewInternal(index, getChildAt(index)); 
requestLayout(); 
invalidate(true); 
} 
/** 
* Removes the specified range of views from the group. 
* 
* @param start the first position in the group of the range of views to remove 
* @param count the number of views to remove 
*/ 
public void removeViews(int start, int count) { 
removeViewsInternal(start, count); 
requestLayout(); 
invalidate(true); 
} 
private void removeViewInternal(View view) { 
final int index = indexOfChild(view); 
if (index = 0) { 
removeViewInternal(index, view); 
} 
} 
private void removeViewInternal(int index, View view) { 
if (mTransition != null) { 
mTransition.removeChild(this, view); 
} 
boolean clearChildFocus = false; 
if (view == mFocused) { 
view.clearFocusForRemoval(); 
clearChildFocus = true; 
} 
if (view.getAnimation() != null || 
(mTransitioningViews != null && mTransitioningViews.contains(view))) { 
addDisappearingView(view); 
} else if (view.mAttachInfo != null) { 
view.dispatchDetachedFromWindow(); 
} 
onViewRemoved(view); 
needGlobalAttributesUpdate(false); 
removeFromArray(index); 
if (clearChildFocus) { 
clearChildFocus(view); 
} 
} 
/** 
* Sets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is 
* not null, changes in layout which occur because of children being added to or removed from 
* the ViewGroup will be animated according to the animations defined in that LayoutTransition 
* object. By default, the transition object is null (so layout changes are not animated). 
* 
* @param transition The LayoutTransition object that will animated changes in layout. A value 
* of codenull/code means no transition will run on layout changes. 
* @attr ref android.R.styleable#ViewGroup_animateLayoutChanges 
*/ 
public void setLayoutTransition(LayoutTransition transition) { 
if (mTransition != null) { 
mTransition.removeTransitionListener(mLayoutTransitionListener); 
} 
mTransition = transition; 
if (mTransition != null) { 
mTransition.addTransitionListener(mLayoutTransitionListener); 
} 
} 
/** 
* Gets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is 
* not null, changes in layout which occur because of children being added to or removed from 
* the ViewGroup will be animated according to the animations defined in that LayoutTransition 
* object. By default, the transition object is null (so layout changes are not animated). 
* 
* @return LayoutTranstion The LayoutTransition object that will animated changes in layout. 
* A value of codenull/code means no transition will run on layout changes. 
*/ 
public LayoutTransition getLayoutTransition() { 
return mTransition; 
} 
private void removeViewsInternal(int start, int count) { 
final View focused = mFocused; 
final boolean detach = mAttachInfo != null; 
View clearChildFocus = null; 
final View[] children = mChildren; 
final int end = start + count; 
for (int i = start; i  end; i++) { 
final View view = children[i]; 
if (mTransition != null) { 
mTransition.removeChild(this, view); 
} 
if (view == focused) { 
view.clearFocusForRemoval(); 
clearChildFocus = view; 
} 
if (view.getAnimation() != null || 
(mTransitioningViews != null && mTransitioningViews.contains(view))) { 
addDisappearingView(view); 
} else if (detach) { 
view.dispatchDetachedFromWindow(); 
} 
needGlobalAttributesUpdate(false); 
onViewRemoved(view); 
} 
removeFromArray(start, count); 
if (clearChildFocus != null) { 
clearChildFocus(clearChildFocus); 
} 
} 
/** 
* Call this method to remove all child views from the 
* ViewGroup. 
*/ 
public void removeAllViews() { 
removeAllViewsInLayout(); 
requestLayout(); 
invalidate(true); 
} 
/** 
* Called by a ViewGroup subclass to remove child views from itself, 
* when it must first know its size on screen before it can calculate how many 
* child views it will render. An example is a Gallery or a ListView, which 
* may "have" 50 children, but actually only render the number of children 
* that can currently fit inside the object on screen. Do not call 
* this method unless you are extending ViewGroup and understand the 
* view measuring and layout pipeline. 
*/ 
public void removeAllViewsInLayout() { 
final int count = mChildrenCount; 
if (count = 0) { 
return; 
} 
final View[] children = mChildren; 
mChildrenCount = 0; 
final View focused = mFocused; 
final boolean detach = mAttachInfo != null; 
View clearChildFocus = null; 
needGlobalAttributesUpdate(false); 
for (int i = count - 1; i = 0; i--) { 
final View view = children[i]; 
if (mTransition != null) { 
mTransition.removeChild(this, view); 
} 
if (view == focused) { 
view.clearFocusForRemoval(); 
clearChildFocus = view; 
} 
if (view.getAnimation() != null || 
(mTransitioningViews != null && mTransitioningViews.contains(view))) { 
addDisappearingView(view); 
} else if (detach) { 
view.dispatchDetachedFromWindow(); 
} 
onViewRemoved(view); 
view.mParent = null; 
children[i] = null; 
} 
if (clearChildFocus != null) { 
clearChildFocus(clearChildFocus); 
} 
} 
/** 
* Finishes the removal of a detached view. This method will dispatch the detached from 
* window event and notify the hierarchy change listener. 
* 
* @param child the child to be definitely removed from the view hierarchy 
* @param animate if true and the view has an animation, the view is placed in the 
* disappearing views list, otherwise, it is detached from the window 
* 
* @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) 
* @see #detachAllViewsFromParent() 
* @see #detachViewFromParent(View) 
* @see #detachViewFromParent(int) 
*/ 
protected void removeDetachedView(View child, boolean animate) { 
if (mTransition != null) { 
mTransition.removeChild(this, child); 
} 
if (child == mFocused) { 
child.clearFocus(); 
} 
if ((animate && child.getAnimation() != null) || 
(mTransitioningViews != null && mTransitioningViews.contains(child))) { 
addDisappearingView(child); 
} else if (child.mAttachInfo != null) { 
child.dispatchDetachedFromWindow(); 
} 
onViewRemoved(child); 
}
同样的,也有查找获得孩子节点的函数:
代码如下:
/** 
* Returns the view at the specified position in the group. 
* 
* @param index the position at which to get the view from 
* @return the view at the specified position or null if the position 
* does not exist within the group 
*/ 
public View getChildAt(int index) { 
if (index  0 || index = mChildrenCount) { 
return null; 
} 
return mChildren[index]; 
}
注:其中具体叶子节点,如Button,它是继承TextView的,TextView是继承View的,代码如下:
代码如下:
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 
。。。 
}
注:其中使用(继承)到ViewGroup类的有我们常用的容器类(包装和容纳各种View),如LinearLayout、FrameLayout等,代码如下:
代码如下:
public class LinearLayout extends ViewGroup { 
public static final int HORIZONTAL = 0; 
public static final int VERTICAL = 1; 
。。。 
} 
public class FrameLayout extends ViewGroup { 
... 
} 
public class RelativeLayout extends ViewGroup { 
private static final String LOG_TAG = "RelativeLayout"; 
private static final boolean DEBUG_GRAPH = false; 
... 
} 
public class AbsoluteLayout extends ViewGroup { 
public AbsoluteLayout(Context context) { 
super(context); 
} 
} 
...
最后送上“基本控件继承关系图”:

本人能力有限,写的很粗糙,恭候大家的批评指正,谢谢~~~