欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-04 10:46:28
组合模式定义: compose objects into tree structures to represent part-whole hierarchies. com...

组合模式定义

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源码学习之组合模式定义及应用

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