组合模式(Composite Pattern)也称为部分整体模式(Part-Whole Pattern),结构型设计模式之一,组合模式比较简单,它将一组相似的对象看做一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别。
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
表示对象的部分-整体结构时。
从一个整体中能够独立出部分模块或功能的场景。
Component:抽象根节点,为组合中的对象的声明接口。在适当情况下,实现实现所有类共有接口的缺省行为。
声明一个接口用于访问和管理Component的子节点。可在递归结构中定义一个接口,用于访问一个父节点,并在合适的地方实现它。
View和ViewGroup的嵌套组合
在Android的这个视图层级中,容器一定是ViewGroup,而且只有ViewGroup才能包含其他View。
@UiThread
public abstract class ViewGroup extends implements ViewParent, ViewManager {
... ...
... ...
}
从继承的角度来说,VIewGroup拥有View类所有的非私有方法,既然如此,两者的差别在于ViewGroup所实现的ViewParent和ViewManager接口上,而事实上也如此,ViewManager接口定义了addView、removeView等对子视图操作的方法。
#ViewManager(接口)
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/(用于向Activity添加和删除子视图的界面。)
(要获取此类的实例,请调用{@link android.content.Context#getSystemService(java.lang.String)Context.getSystemService()})
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.(将传递的LayoutParams分配给传递的视图,并将视图添加到窗口。)
* Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
(对于某些编程错误,抛出{@link android.view.WindowManager.BadTokenException},例如在不删除第一个视图的情况下向窗口添加第二个视图。)
*
Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
(如果窗口位于辅助{@link Display}上并且无法找到指定的显示,则抛出{@link android.view.WindowManager.InvalidDisplayException}(请参阅{@link android.app.Presentation})。)
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
而ViewParent则定义了刷新容器的接口 requestLayout和其他一些焦点事件的处理的接口。
/**
* Defines the responsibilities for a class that will be a parent of a View.
* This is the API that a view sees when it wants to interact with its parent.
* (定义将成为View父级的类的职责。
这是视图在想要与其父级交互时看到的API。(子View与父View交互的API))
*/
public interface ViewParent {
/**
* Called when something has changed which has invalidated the layout of a
* child of this view parent. This will schedule a layout pass of the view
* tree.(当某些内容发生变化时调用,这会使该视图父项的子项布局无效。 这将安排视图树的布局传递。)
*/
//请求从新布局
public void requestLayout();
/**
* Indicates whether layout was requested on this view parent.
*(指示是否在此视图父级上请求布局。)
* @return true if layout was requested, false otherwise
*/
//是否已经请求布局 这里有一点需要注意 当我们调用requestLayout请求布局后,这一过程并非立即执行的,Android会将请求布局的操作以消息的形式发送给主线程的Handler并由其分发处理。
因此,在调用requestLayout方法请求布局到真正接收到重新布局的命令需要一段时间间隔。
public boolean isLayoutRequested();
/**
(当孩子希望视图层次结构收集并向窗口合成器报告透明区域时调用。 在视图层次结构中“打孔”的视图(例如SurfaceView)可以使用此API来提高系统性能。 如果层次结构中不存在此类视图,则此不必要的优化可能会略微降低视图层次结构的性能。)
* Called when a child wants the view hierarchy to gather and report
* transparent regions to the window compositor. Views that "punch" holes in
* the view hierarchy, such as SurfaceView can use this API to improve
* performance of the system. When no such a view is present in the
* hierarchy, this optimization in unnecessary and might slightly reduce the
* view hierarchy performance.
*
* @param child the view requesting the transparent region computation
*
*/
//当子视图需要收集视图层次中透明区域并报告给窗口排版组件时调用,需要在视图层次中“打洞”的视图,如果SurfaceView可以利用该API来提高系统性能,当视图层次中没有这样的视图时,不需要该优化,使它会稍微降低一些视图层次的性能。
public void requestTransparentRegion(View child);
/**
* The target View has been invalidated, or has had a drawing property changed that
* requires the hierarchy to re-render.
*
* This method is called by the View hierarchy to signal ancestors that a View either needs to
* re-record its drawing commands, or drawing properties have changed. This is how Views
* schedule a drawing traversal.
*
* This signal is generally only dispatched for attached Views, since only they need to draw.
*
* @param child Direct child of this ViewParent containing target
* @param target The view that needs to redraw
*/
default void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
if (getParent() != null) {
// Note: should pass 'this' as default, but can't since we may not be a View
getParent().onDescendantInvalidated(child, target);
}
}
/**
* All or part of a child is dirty and needs to be redrawn.
* (全部或部分子View需要重新绘制。)
* @param child The child which is dirty
* @param r The area within the child that is invalid
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
*/
@Deprecated
public void invalidateChild(View child, Rect r);(无效子视图)
/**
* All or part of a child is dirty and needs to be redrawn.
*
* The location array is an array of two int values which respectively
* define the left and the top position of the dirty child.
*
* This method must return the parent of this ViewParent if the specified
* rectangle must be invalidated in the parent. If the specified rectangle
* does not require invalidation in the parent or if the parent does not
* exist, this method must return null.
*
* When this method returns a non-null value, the location array must
* have been updated with the left and top coordinates of this ViewParent.
*
* @param location An array of 2 ints containing the left and top
* coordinates of the child to invalidate
* @param r The area within the child that is invalid
*
* @return the parent of this ViewParent or null
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
*/
@Deprecated
public ViewParent invalidateChildInParent(int[] location, Rect r);(无效子视图的部分或者全部区域)
/**
* Returns the parent if it exists, or null.
*(返回父项(如果存在)或null。)
* @return a ViewParent or null if this ViewParent does not have a parent
*/
//获取View的当前ViewParent对象
public ViewParent getParent();
/**
* Called when a child of this parent wants focus
* (当这个父View的子View想要获取焦点时调用)
* @param child The child of this ViewParent that wants focus. This view
* will contain the focused view. It is not necessarily the view that
* actually has focus.
* @param focused The view that is a descendant of child that actually has
* focus
*/
//请求子视图的焦点
public void requestChildFocus(View child, View focused);
/**
* Tell view hierarchy that the global view attributes need to be
* re-evaluated.(告诉视图层次结构需要重新评估全局视图属性)
*
* @param child View whose attributes have changed.
*/
public void recomputeViewAttributes(View child);
/**
* Called when a child of this parent is giving up focus
* (当这个父View的子VIew放弃焦点时调用)
* @param child The view that is giving up focus
*/
public void clearChildFocus(View child);
/**
* Compute the visible part of a rectangular region defined in terms of a child view's
* coordinates.
*
* Returns the clipped visible part of the rectangle r
, defined in the
* child
's local coordinate system. r
is modified by this method to
* contain the result, expressed in the global (root) coordinate system.
*
* The resulting rectangle is always axis aligned. If a rotation is applied to a node in the
* View hierarchy, the result is the axis-aligned bounding box of the visible rectangle.
*
* @param child A child View, whose rectangular visible region we want to compute
* @param r The input rectangle, defined in the child coordinate system. Will be overwritten to
* contain the resulting visible rectangle, expressed in global (root) coordinates
* @param offset The input coordinates of a point, defined in the child coordinate system.
* As with the r
parameter, this will be overwritten to contain the global (root)
* coordinates of that point.
* A null
value is valid (in case you are not interested in this result)
* @return true if the resulting rectangle is not empty, false otherwise
*/
public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset);
/**
* Find the nearest view in the specified direction that wants to take focus
*
* @param v The view that currently has focus
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
*/
public View focusSearch(View v, int direction);
/**
* Find the nearest keyboard navigation cluster in the specified direction.
* This does not actually give focus to that cluster.
*
* @param currentCluster The starting point of the search. Null means the current cluster is not
* found yet
* @param direction Direction to look
*
* @return The nearest keyboard navigation cluster in the specified direction, or null if none
* can be found
*/
View keyboardNavigationClusterSearch(View currentCluster, int direction);
/**
* Change the z order of the child so it's on top of all other children.
* This ordering change may affect layout, if this container
* uses an order-dependent layout scheme (e.g., LinearLayout). Prior
* to {@link android.os.Build.VERSION_CODES#KITKAT} this
* method should be followed by calls to {@link #requestLayout()} and
* {@link View#invalidate()} on this parent to force the parent to redraw
* with the new child ordering.
*
* @param child The child to bring to the top of the z order
*/
public void bringChildToFront(View child);
/**
* Tells the parent that a new focusable view has become available. This is
* to handle transitions from the case where there are no focusable views to
* the case where the first focusable view appears.
*
* @param v The view that has become newly focusable
*/
public void focusableViewAvailable(View v);
/**
* Shows the context menu for the specified view or its ancestors.
*
* In most cases, a subclass does not need to override this. However, if
* the subclass is added directly to the window manager (for example,
* {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
* then it should override this and show the context menu.
*
* @param originalView the source view where the context menu was first
* invoked
* @return {@code true} if the context menu was shown, {@code false}
* otherwise
* @see #showContextMenuForChild(View, float, float)
*/
public boolean showContextMenuForChild(View originalView);
/**
* Shows the context menu for the specified view or its ancestors anchored
* to the specified view-relative coordinate.
*
* In most cases, a subclass does not need to override this. However, if
* the subclass is added directly to the window manager (for example,
* {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
* then it should override this and show the context menu.
*
* If a subclass overrides this method it should also override
* {@link #showContextMenuForChild(View)}.
*
* @param originalView the source view where the context menu was first
* invoked
* @param x the X coordinate in pixels relative to the original view to
* which the menu should be anchored, or {@link Float#NaN} to
* disable anchoring
* @param y the Y coordinate in pixels relative to the original view to
* which the menu should be anchored, or {@link Float#NaN} to
* disable anchoring
* @return {@code true} if the context menu was shown, {@code false}
* otherwise
*/
boolean showContextMenuForChild(View originalView, float x, float y);
/**
* Have the parent populate the specified context menu if it has anything to
* add (and then recurse on its parent).
*
* @param menu The menu to populate
*/
public void createContextMenu(ContextMenu menu);
/**
* Start an action mode for the specified view with the default type
* {@link ActionMode#TYPE_PRIMARY}.
*
*
In most cases, a subclass does not need to override this. However, if the
* subclass is added directly to the window manager (for example,
* {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
* then it should override this and start the action mode.
*
* @param originalView The source view where the action mode was first invoked
* @param callback The callback that will handle lifecycle events for the action mode
* @return The new action mode if it was started, null otherwise
*
* @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
*/
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
... ...
}
其中有一些方法我们是比较常见的也会经常用到,如requestLayout、bringChildToFront等,ViewGroup除了实现的这俩个接口与View不同以外,还有重要的一点是ViewGroup是抽象类,其将View中的onLayout方法重置为抽象方法,也就是容器子类必须实现该方法来实现布局定位,我们知道View中的该方法是一个空实现,因为对于一个普通的View来说该方法没有实现价值。但是ViewGroup不一样,要必须实现。除此以外,在View中比较重要的两个测绘流程的方法onMeasure和onDraw在ViewGroup中都没有被重写,相对于onMeasure方法,在ViewGroup中增加了一些计算子View的方法,如measureChildren等;而对于onDraw方法,ViewGroup定义了一个dispatchDraw方法来调用其每一个子View的onDraw方法,由此可见,ViewGroup真的就像一个容器一样,其职责只是负责对子元素的操作而非具体的个体行为。
参考《Android源码设计模式》