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源码中,都能找到使用组合模式的例子,其中在《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 <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); } } ...
四、基本控件继承关系图
最后送上“基本控件继承关系图”:
本人能力有限,写的很粗糙,恭候大家的批评指正,谢谢~~~