Android源码学习之组合模式定义及应用

时间:2021-11-13 07:03:00

组合模式定义

compose objects into tree structures to represent part-whole hierarchies. composite lets clients treat individual objects and compositions of objects uniformly.

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

Android源码学习之组合模式定义及应用

如上图所示(截取自《head first design patterns》一书),主要包括三个部分

1. component抽象组件。定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。

2. leaf叶子节点。构成组合树的最小构建单元。

3. composite树枝节点组件。它的作用是组合树枝节点和叶子节点形成一个树形结构。

高层模块调用简单。一棵树形结构的所有节点都是component,局部和整体对调用者来说都是一样的,没有区别,所以高层模块不比关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。

节点*扩展增加。使用组合模式,如果想增加一个树枝节点或者叶子节点都是很简单的,只要找到它的父节点就可以了,非常容易扩展,符合“开闭原则”。

应用最广的模式之一。应用在维护和展示部分-整体关系的场景,如树形菜单、文件夹管理等等。

在android源码中,都能找到使用组合模式的例子,其中在《android源码学习之观察者模式应用》介绍到的viewgroup和view的结构就是一个组合模式,结构图如下所示:

Android源码学习之组合模式定义及应用

现在来看看它们是如何利用组合模式组织在一起的,首先在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 <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);
}
}
...

 

最后送上“基本控件继承关系图”:
Android源码学习之组合模式定义及应用

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