Android源码学习之组合模式定义及应用
组合模式定义:
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抽象组件。定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。
2. leaf叶子节点。构成组合树的最小构建单元。
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);
}
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 <code>null</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 <code>null</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);
}
}
...
最后送上“基本控件继承关系图”:
本人能力有限,写的很粗糙,恭候大家的批评指正,谢谢~~~