/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import android.animation.LayoutTransition; import android.annotation.CallSuper; import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.TestApi; import android.annotation.UiThread; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.util.Pools; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; import android.view.animation.Transformation; import com.android.internal.R; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Predicate; /** *
* A ViewGroup
is a special view that can contain other views
* (called children.) The view group is the base class for layouts and views
* containers. This class also defines the
* {@link android.view.ViewGroup.LayoutParams} class which serves as the base
* class for layouts parameters.
*
* Also see {@link LayoutParams} for layout attributes. *
* *For more information about creating user interface layouts, read the * XML Layouts developer * guide.
Here is a complete implementation of a custom ViewGroup that implements * a simple {@link android.widget.FrameLayout} along with the ability to stack * children in left and right gutters.
* * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java * Complete} * *If you are implementing XML layout attributes as shown in the example, this is the
* corresponding definition for them that would go in res/values/attrs.xml
:
Finally the layout manager can be used in an XML layout like so:
* * {@sample development/samples/ApiDemos/res/layout/custom_layout.xml Complete} * * @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 * @attr ref android.R.styleable#ViewGroup_splitMotionEvents * @attr ref android.R.styleable#ViewGroup_layoutMode */ @UiThread public abstract class ViewGroup extends View implements ViewParent, ViewManager { private static final String TAG = "ViewGroup"; private static final boolean DBG = false; /** * Views which have been hidden or removed which need to be animated on * their way out. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected ArrayList* If an {@link android.view.View.AccessibilityDelegate} has been specified via calling * {@link android.view.View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its * {@link android.view.View.AccessibilityDelegate#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)} * is responsible for handling this call. *
* * @param child The child which requests sending the event. * @param event The event to be sent. * @return True if the event should be sent. * * @see #requestSendAccessibilityEvent(View, AccessibilityEvent) */ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { if (mAccessibilityDelegate != null) { return mAccessibilityDelegate.onRequestSendAccessibilityEvent(this, child, event); } else { return onRequestSendAccessibilityEventInternal(child, event); } } /** * @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent) * * Note: Called from the default {@link View.AccessibilityDelegate}. * * @hide */ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { return true; } /** * Called when a child view has changed whether or not it is tracking transient state. */ @Override public void childHasTransientStateChanged(View child, boolean childHasTransientState) { final boolean oldHasTransientState = hasTransientState(); if (childHasTransientState) { mChildCountWithTransientState++; } else { mChildCountWithTransientState--; } final boolean newHasTransientState = hasTransientState(); if (mParent != null && oldHasTransientState != newHasTransientState) { try { mParent.childHasTransientStateChanged(this, newHasTransientState); } catch (AbstractMethodError e) { Log.e(TAG, mParent.getClass().getSimpleName() + " does not fully implement ViewParent", e); } } } @Override public boolean hasTransientState() { return mChildCountWithTransientState > 0 || super.hasTransientState(); } @Override public boolean dispatchUnhandledMove(View focused, int direction) { return mFocused != null && mFocused.dispatchUnhandledMove(focused, direction); } @Override public void clearChildFocus(View child) { if (DBG) { System.out.println(this + " clearChildFocus()"); } mFocused = null; if (mParent != null) { mParent.clearChildFocus(this); } } @Override public void clearFocus() { if (DBG) { System.out.println(this + " clearFocus()"); } if (mFocused == null) { super.clearFocus(); } else { View focused = mFocused; mFocused = null; focused.clearFocus(); } } @Override void unFocus(View focused) { if (DBG) { System.out.println(this + " unFocus()"); } if (mFocused == null) { super.unFocus(focused); } else { mFocused.unFocus(focused); mFocused = null; } } /** * Returns the focused child of this view, if any. The child may have focus * or contain focus. * * @return the focused child or null. */ public View getFocusedChild() { return mFocused; } View getDeepestFocusedChild() { View v = this; while (v != null) { if (v.isFocused()) { return v; } v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null; } return null; } /** * Returns true if this view has or contains focus * * @return true if this view has or contains focus */ @Override public boolean hasFocus() { return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null; } /* * (non-Javadoc) * * @see android.view.View#findFocus() */ @Override public View findFocus() { if (DBG) { System.out.println("Find focus in " + this + ": flags=" + isFocused() + ", child=" + mFocused); } if (isFocused()) { return this; } if (mFocused != null) { return mFocused.findFocus(); } return null; } @Override boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) { // This should probably be super.hasFocusable, but that would change // behavior. Historically, we have not checked the ancestor views for // shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable. // Invisible and gone views are never focusable. if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) { return false; } // Only use effective focusable value when allowed. if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) { return true; } // Determine whether we have a focused descendant. final int descendantFocusability = getDescendantFocusability(); if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { return hasFocusableChild(dispatchExplicit); } return false; } boolean hasFocusableChild(boolean dispatchExplicit) { // Determine whether we have a focusable descendant. final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; // In case the subclass has overridden has[Explicit]Focusable, dispatch // to the expected one for each child even though we share logic here. if ((dispatchExplicit && child.hasExplicitFocusable()) || (!dispatchExplicit && child.hasFocusable())) { return true; } } return false; } @Override public void addFocusables(ArrayList* This method is called before dispatching a hover event to a child of * the view group or to the view group's own {@link #onHoverEvent} to allow * the view group a chance to intercept the hover event. * This method can also be used to watch all pointer motions that occur within * the bounds of the view group even when the pointer is hovering over * a child of the view group rather than over the view group itself. *
* The view group can prevent its children from receiving hover events by
* implementing this method and returning true
to indicate
* that it would like to intercept hover events. The view group must
* continuously return true
from {@link #onInterceptHoverEvent}
* for as long as it wishes to continue intercepting hover events from
* its children.
*
* Interception preserves the invariant that at most one view can be * hovered at a time by transferring hover focus from the currently hovered * child to the view group or vice-versa as needed. *
* If this method returns true
and a child is already hovered, then the
* child view will first receive a hover exit event and then the view group
* itself will receive a hover enter event in {@link #onHoverEvent}.
* Likewise, if this method had previously returned true
to intercept hover
* events and instead returns false
while the pointer is hovering
* within the bounds of one of a child, then the view group will first receive a
* hover exit event in {@link #onHoverEvent} and then the hovered child will
* receive a hover enter event.
*
* The default implementation handles mouse hover on the scroll bars. *
* * @param event The motion event that describes the hover. * @return True if the view group would like to intercept the hover event * and prevent its children from receiving it. */ public boolean onInterceptHoverEvent(MotionEvent event) { if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { final int action = event.getAction(); final float x = event.getX(); final float y = event.getY(); if ((action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) { return true; } } return false; } private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) { if (event.getHistorySize() == 0) { return event; } return MotionEvent.obtainNoHistory(event); } @Override protected boolean dispatchGenericPointerEvent(MotionEvent event) { // Send the event to the child under the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { final float x = event.getX(); final float y = event.getY(); final ArrayListWhen this option is enabled MotionEvents may be split and dispatched to different child
* views depending on where each pointer initially went down. This allows for user interactions
* such as scrolling two panes of content independently, chording of buttons, and performing
* independent gestures on different pieces of content.
*
* @param split true
to allow MotionEvents to be split and dispatched to multiple
* child views. false
to only allow one child view to be the target of
* any MotionEvent received by this ViewGroup.
* @attr ref android.R.styleable#ViewGroup_splitMotionEvents
*/
public void setMotionEventSplittingEnabled(boolean split) {
// TODO Applications really shouldn't change this setting mid-touch event,
// but perhaps this should handle that case and send ACTION_CANCELs to any child views
// with gestures in progress when this is changed.
if (split) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
} else {
mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
}
}
/**
* Returns true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
* @return true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
*/
public boolean isMotionEventSplittingEnabled() {
return (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS;
}
/**
* Returns true if this ViewGroup should be considered as a single entity for removal
* when executing an Activity transition. If this is false, child elements will move
* individually during the transition.
*
* @return True if the ViewGroup should be acted on together during an Activity transition.
* The default value is true when there is a non-null background or if
* {@link #getTransitionName()} is not null or if a
* non-null {@link android.view.ViewOutlineProvider} other than
* {@link android.view.ViewOutlineProvider#BACKGROUND} was given to
* {@link #setOutlineProvider(ViewOutlineProvider)} and false otherwise.
*/
public boolean isTransitionGroup() {
if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
} else {
final ViewOutlineProvider outlineProvider = getOutlineProvider();
return getBackground() != null || getTransitionName() != null ||
(outlineProvider != null && outlineProvider != ViewOutlineProvider.BACKGROUND);
}
}
/**
* Changes whether or not this ViewGroup should be treated as a single entity during
* Activity Transitions.
* @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit
* in Activity transitions. If false, the ViewGroup won't transition,
* only its children. If true, the entire ViewGroup will transition
* together.
* @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
* android.util.Pair[])
*/
public void setTransitionGroup(boolean isTransitionGroup) {
mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;
if (isTransitionGroup) {
mGroupFlags |= FLAG_IS_TRANSITION_GROUP;
} else {
mGroupFlags &= ~FLAG_IS_TRANSITION_GROUP;
}
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
*
Using this function takes some care, as it has a fairly complicated * interaction with {@link View#onTouchEvent(MotionEvent) * View.onTouchEvent(MotionEvent)}, and using it requires implementing * that method as well as this one in the correct way. Events will be * received in the following order: * *
This implementation adds in all child views of the view group, in addition to calling the
* default {@link View} implementation.
*/
@Override
public void dispatchProvideAutofillStructure(ViewStructure structure,
@AutofillFlags int flags) {
super.dispatchProvideAutofillStructure(structure, flags);
if (structure.getChildCount() != 0) {
return;
}
if (!isLaidOut()) {
Log.v(VIEW_LOG_TAG, "dispatchProvideAutofillStructure(): not laid out, ignoring "
+ mChildrenCount + " children of " + getAutofillId());
return;
}
final ChildListForAutoFill children = getChildrenForAutofill(flags);
final int childrenCount = children.size();
structure.setChildCount(childrenCount);
for (int i = 0; i < childrenCount; i++) {
final View child = children.get(i);
final ViewStructure cstructure = structure.newChild(i);
child.dispatchProvideAutofillStructure(cstructure, flags);
}
children.recycle();
}
/**
* Gets the children for autofill. Children for autofill are the first
* level descendants that are important for autofill. The returned
* child list object is pooled and the caller must recycle it once done.
* @hide */
private @NonNull ChildListForAutoFill getChildrenForAutofill(@AutofillFlags int flags) {
final ChildListForAutoFill children = ChildListForAutoFill.obtain();
populateChildrenForAutofill(children, flags);
return children;
}
/** @hide */
private void populateChildrenForAutofill(ArrayList Subclasses should always call This function is called automatically when the developer setting is enabled. It is strongly advised to only call this function from debug builds as there is
* a risk of leaking unwanted layout information. This function is called automatically when the developer setting is enabled. It is strongly advised to only call this function from debug builds as there is
* a risk of leaking unwanted layout information. Note: Overlays do not currently work correctly with {@link
* SurfaceView} or {@link TextureView}; contents in overlays for these
* types of views may not display correctly.
* NOTE: In order for this method to be called, you must enable child ordering
* first by calling {@link #setChildrenDrawingOrderEnabled(boolean)}.
*
* @param i The current iteration.
* @return The index of the child to draw this iteration.
*
* @see #setChildrenDrawingOrderEnabled(boolean)
* @see #isChildrenDrawingOrderEnabled()
*/
protected int getChildDrawingOrder(int childCount, int i) {
return i;
}
private boolean hasChildWithZ() {
for (int i = 0; i < mChildrenCount; i++) {
if (mChildren[i].getZ() != 0) return true;
}
return false;
}
/**
* Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,
* sorted first by Z, then by child drawing order (if applicable). This list must be cleared
* after use to avoid leaking child Views.
*
* Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
* children.
*/
ArrayList
* By default, children are clipped to the padding of their parent
* ViewGroup. This clipping behavior is only enabled if padding is non-zero.
*
* @param clipToPadding true to clip children to the padding of the group, and resize (but
* not clip) any EdgeEffect to the padded region. False otherwise.
* @attr ref android.R.styleable#ViewGroup_clipToPadding
*/
public void setClipToPadding(boolean clipToPadding) {
if (hasBooleanFlag(FLAG_CLIP_TO_PADDING) != clipToPadding) {
setBooleanFlag(FLAG_CLIP_TO_PADDING, clipToPadding);
invalidate(true);
}
}
/**
* Returns whether this ViewGroup will clip its children to its padding, and resize (but
* not clip) any EdgeEffect to the padded region, if padding is present.
*
* By default, children are clipped to the padding of their parent
* Viewgroup. This clipping behavior is only enabled if padding is non-zero.
*
* @return true if this ViewGroup clips children to its padding and resizes (but doesn't
* clip) any EdgeEffect to the padded region, false otherwise.
*
* @attr ref android.R.styleable#ViewGroup_clipToPadding
*/
@ViewDebug.ExportedProperty(category = "drawing")
public boolean getClipToPadding() {
return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
}
@Override
public void dispatchSetSelected(boolean selected) {
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = 0; i < count; i++) {
children[i].setSelected(selected);
}
}
@Override
public void dispatchSetActivated(boolean activated) {
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = 0; i < count; i++) {
children[i].setActivated(activated);
}
}
@Override
protected void dispatchSetPressed(boolean pressed) {
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = 0; i < count; i++) {
final View child = children[i];
// Children that are clickable on their own should not
// show a pressed state when their parent view does.
// Clearing a pressed state always propagates.
if (!pressed || (!child.isClickable() && !child.isLongClickable())) {
child.setPressed(pressed);
}
}
}
/**
* Dispatches drawable hotspot changes to child views that meet at least
* one of the following criteria:
* Note that since transient views do not participate in layout, they must be sized
* manually or, more typically, they should just use the size that they had before they
* were removed from their container. Transient views are useful for handling animations of views that have been removed
* from the container, but which should be animated out after the removal. Adding these
* views as transient views allows them to participate in drawing without side-effecting
* the layout of the container. Transient views must always be explicitly {@link #removeTransientView(View) removed}
* from the container when they are no longer needed. For example, a transient view
* which is added in order to fade it out in its old location should be removed
* once the animation is complete. 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. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Replacing a non-null transition will cause that previous transition to be
* canceled, if it is currently running, to restore this container to
* its correct post-transition state. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method. Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.
* This method is intended to be lightweight and makes no assumptions about whether the
* parent or child should be redrawn. Proper use of this method will include also making
* any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
* For example, callers can {@link #post(Runnable) post} a {@link Runnable}
* which performs a {@link #requestLayout()} on the next frame, after all detach/remove
* calls are finished, causing layout to be run prior to redrawing the view hierarchy.
*
* @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 (child == mDefaultFocus) {
clearDefaultFocus(child);
}
if (child == mFocusedInCluster) {
clearFocusedInCluster(child);
}
child.clearAccessibilityFocus();
cancelTouchTarget(child);
cancelHoverTarget(child);
if ((animate && child.getAnimation() != null) ||
(mTransitioningViews != null && mTransitioningViews.contains(child))) {
addDisappearingView(child);
} else if (child.mAttachInfo != null) {
child.dispatchDetachedFromWindow();
}
if (child.hasTransientState()) {
childHasTransientStateChanged(child, false);
}
dispatchViewRemoved(child);
}
/**
* Attaches a view to this view group. Attaching a view assigns this group as the parent,
* sets the layout parameters and puts the view in the list of children so that
* it can be retrieved by calling {@link #getChildAt(int)}.
*
* This method is intended to be lightweight and makes no assumptions about whether the
* parent or child should be redrawn. Proper use of this method will include also making
* any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
* For example, callers can {@link #post(Runnable) post} a {@link Runnable}
* which performs a {@link #requestLayout()} on the next frame, after all detach/attach
* calls are finished, causing layout to be run prior to redrawing the view hierarchy.
*
* This method should be called only for views which were detached from their parent.
*
* @param child the child to attach
* @param index the index at which the child should be attached
* @param params the layout parameters of the child
*
* @see #removeDetachedView(View, boolean)
* @see #detachAllViewsFromParent()
* @see #detachViewFromParent(View)
* @see #detachViewFromParent(int)
*/
protected void attachViewToParent(View child, int index, LayoutParams params) {
child.mLayoutParams = params;
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
child.mParent = this;
child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK
& ~PFLAG_DRAWING_CACHE_VALID)
| PFLAG_DRAWN | PFLAG_INVALIDATED;
this.mPrivateFlags |= PFLAG_INVALIDATED;
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
}
dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE
&& isShown());
}
/**
* Detaches a view from its parent. Detaching a view should be followed
* either by a call to
* {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
* or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
* temporary; reattachment or removal should happen within the same drawing cycle as
* detachment. When a view is detached, its parent is null and cannot be retrieved by a
* call to {@link #getChildAt(int)}.
*
* @param child the child to detach
*
* @see #detachViewFromParent(int)
* @see #detachViewsFromParent(int, int)
* @see #detachAllViewsFromParent()
* @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
* @see #removeDetachedView(View, boolean)
*/
protected void detachViewFromParent(View child) {
removeFromArray(indexOfChild(child));
}
/**
* Detaches a view from its parent. Detaching a view should be followed
* either by a call to
* {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
* or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
* temporary; reattachment or removal should happen within the same drawing cycle as
* detachment. When a view is detached, its parent is null and cannot be retrieved by a
* call to {@link #getChildAt(int)}.
*
* @param index the index of the child to detach
*
* @see #detachViewFromParent(View)
* @see #detachAllViewsFromParent()
* @see #detachViewsFromParent(int, int)
* @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
* @see #removeDetachedView(View, boolean)
*/
protected void detachViewFromParent(int index) {
removeFromArray(index);
}
/**
* Detaches a range of views from their parents. Detaching a view should be followed
* either by a call to
* {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
* or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
* temporary; reattachment or removal should happen within the same drawing cycle as
* detachment. When a view is detached, its parent is null and cannot be retrieved by a
* call to {@link #getChildAt(int)}.
*
* @param start the first index of the childrend range to detach
* @param count the number of children to detach
*
* @see #detachViewFromParent(View)
* @see #detachViewFromParent(int)
* @see #detachAllViewsFromParent()
* @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
* @see #removeDetachedView(View, boolean)
*/
protected void detachViewsFromParent(int start, int count) {
removeFromArray(start, count);
}
/**
* Detaches all views from the parent. Detaching a view should be followed
* either by a call to
* {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
* or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
* temporary; reattachment or removal should happen within the same drawing cycle as
* detachment. When a view is detached, its parent is null and cannot be retrieved by a
* call to {@link #getChildAt(int)}.
*
* @see #detachViewFromParent(View)
* @see #detachViewFromParent(int)
* @see #detachViewsFromParent(int, int)
* @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
* @see #removeDetachedView(View, boolean)
*/
protected void detachAllViewsFromParent() {
final int count = mChildrenCount;
if (count <= 0) {
return;
}
final View[] children = mChildren;
mChildrenCount = 0;
for (int i = count - 1; i >= 0; i--) {
children[i].mParent = null;
children[i] = null;
}
}
@Override
@CallSuper
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
/*
* HW-only, Rect-ignoring damage codepath
*
* We don't deal with rectangles here, since RenderThread native code computes damage for
* everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
*/
// if set, combine the animation flag into the parent
mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
// We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
// optimization in provides in a DisplayList world.
mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
// simplified invalidateChildInParent behavior: clear cache validity to be safe...
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// ... and mark inval if in software layer that needs to repaint (hw handled in native)
if (mLayerType == LAYER_TYPE_SOFTWARE) {
// Layered parents should be invalidated. Escalate to a full invalidate (and note that
// we do this after consuming any relevant flags from the originating descendant)
mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
target = this;
}
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
* draw state in descendants.
*/
@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
if (attachInfo != null) {
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
// Check whether the child that requests the invalidate is fully opaque
// Views being animated or transformed are not considered opaque because we may
// be invalidating their old position and need the parent to paint behind them.
Matrix childMatrix = child.getMatrix();
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
if (child.mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
if (!childMatrix.isIdentity() ||
(mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
Matrix transformMatrix;
if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
Transformation t = attachInfo.mTmpTransformation;
boolean transformed = getChildStaticTransformation(child, t);
if (transformed) {
transformMatrix = attachInfo.mTmpMatrix;
transformMatrix.set(t.getMatrix());
if (!childMatrix.isIdentity()) {
transformMatrix.preConcat(childMatrix);
}
} else {
transformMatrix = childMatrix;
}
} else {
transformMatrix = childMatrix;
}
transformMatrix.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
* This implementation returns null if this ViewGroup does not have a parent,
* if this ViewGroup is already fully invalidated or if the dirty rectangle
* does not intersect with this ViewGroup's bounds.
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
* draw state in descendants.
*/
@Deprecated
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
// either DRAWN, or DRAWING_CACHE_VALID
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
!= FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
} else {
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
return mParent;
}
return null;
}
/**
* Offset a rectangle that is in a descendant's coordinate
* space into our coordinate space.
* @param descendant A descendant of this view
* @param rect A rectangle defined in descendant's coordinate space.
*/
public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) {
offsetRectBetweenParentAndChild(descendant, rect, true, false);
}
/**
* Offset a rectangle that is in our coordinate space into an ancestor's
* coordinate space.
* @param descendant A descendant of this view
* @param rect A rectangle defined in descendant's coordinate space.
*/
public final void offsetRectIntoDescendantCoords(View descendant, Rect rect) {
offsetRectBetweenParentAndChild(descendant, rect, false, false);
}
/**
* Helper method that offsets a rect either from parent to descendant or
* descendant to parent.
*/
void offsetRectBetweenParentAndChild(View descendant, Rect rect,
boolean offsetFromChildToParent, boolean clipToBounds) {
// already in the same coord system :)
if (descendant == this) {
return;
}
ViewParent theParent = descendant.mParent;
// search and offset up to the parent
while ((theParent != null)
&& (theParent instanceof View)
&& (theParent != this)) {
if (offsetFromChildToParent) {
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
if (clipToBounds) {
View p = (View) theParent;
boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
p.mBottom - p.mTop);
if (!intersected) {
rect.setEmpty();
}
}
} else {
if (clipToBounds) {
View p = (View) theParent;
boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
p.mBottom - p.mTop);
if (!intersected) {
rect.setEmpty();
}
}
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
descendant = (View) theParent;
theParent = descendant.mParent;
}
// now that we are up to this view, need to offset one more time
// to get into our coordinate space
if (theParent == this) {
if (offsetFromChildToParent) {
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
} else {
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
} else {
throw new IllegalArgumentException("parameter must be a descendant of this view");
}
}
/**
* Offset the vertical location of all children of this view by the specified number of pixels.
*
* @param offset the number of pixels to offset
*
* @hide
*/
public void offsetChildrenTopAndBottom(int offset) {
final int count = mChildrenCount;
final View[] children = mChildren;
boolean invalidate = false;
for (int i = 0; i < count; i++) {
final View v = children[i];
v.mTop += offset;
v.mBottom += offset;
if (v.mRenderNode != null) {
invalidate = true;
v.mRenderNode.offsetTopAndBottom(offset);
}
}
if (invalidate) {
invalidateViewProperty(false, false);
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
@Override
public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
return getChildVisibleRect(child, r, offset, false);
}
/**
* @param forceParentCheck true to guarantee that this call will propagate to all ancestors,
* false otherwise
*
* @hide
*/
public boolean getChildVisibleRect(
View child, Rect r, android.graphics.Point offset, boolean forceParentCheck) {
// It doesn't make a whole lot of sense to call this on a view that isn't attached,
// but for some simple tests it can be useful. If we don't have attach info this
// will allocate memory.
final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
rect.set(r);
if (!child.hasIdentityMatrix()) {
child.getMatrix().mapRect(rect);
}
final int dx = child.mLeft - mScrollX;
final int dy = child.mTop - mScrollY;
rect.offset(dx, dy);
if (offset != null) {
if (!child.hasIdentityMatrix()) {
float[] position = mAttachInfo != null ? mAttachInfo.mTmpTransformLocation
: new float[2];
position[0] = offset.x;
position[1] = offset.y;
child.getMatrix().mapPoints(position);
offset.x = Math.round(position[0]);
offset.y = Math.round(position[1]);
}
offset.x += dx;
offset.y += dy;
}
final int width = mRight - mLeft;
final int height = mBottom - mTop;
boolean rectIsVisible = true;
if (mParent == null ||
(mParent instanceof ViewGroup && ((ViewGroup) mParent).getClipChildren())) {
// Clip to bounds.
rectIsVisible = rect.intersect(0, 0, width, height);
}
if ((forceParentCheck || rectIsVisible)
&& (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
// Clip to padding.
rectIsVisible = rect.intersect(mPaddingLeft, mPaddingTop,
width - mPaddingRight, height - mPaddingBottom);
}
if ((forceParentCheck || rectIsVisible) && mClipBounds != null) {
// Clip to clipBounds.
rectIsVisible = rect.intersect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
mClipBounds.bottom);
}
r.set((int) Math.floor(rect.left), (int) Math.floor(rect.top),
(int) Math.ceil(rect.right), (int) Math.ceil(rect.bottom));
if ((forceParentCheck || rectIsVisible) && mParent != null) {
if (mParent instanceof ViewGroup) {
rectIsVisible = ((ViewGroup) mParent)
.getChildVisibleRect(this, r, offset, forceParentCheck);
} else {
rectIsVisible = mParent.getChildVisibleRect(this, r, offset);
}
}
return rectIsVisible;
}
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
/**
* Indicates whether the view group has the ability to animate its children
* after the first layout.
*
* @return true if the children can be animated, false otherwise
*/
protected boolean canAnimate() {
return mLayoutAnimationController != null;
}
/**
* Runs the layout animation. Calling this method triggers a relayout of
* this view group.
*/
public void startLayoutAnimation() {
if (mLayoutAnimationController != null) {
mGroupFlags |= FLAG_RUN_ANIMATION;
requestLayout();
}
}
/**
* Schedules the layout animation to be played after the next layout pass
* of this view group. This can be used to restart the layout animation
* when the content of the view group changes or when the activity is
* paused and resumed.
*/
public void scheduleLayoutAnimation() {
mGroupFlags |= FLAG_RUN_ANIMATION;
}
/**
* Sets the layout animation controller used to animate the group's
* children after the first layout.
*
* @param controller the animation controller
*/
public void setLayoutAnimation(LayoutAnimationController controller) {
mLayoutAnimationController = controller;
if (mLayoutAnimationController != null) {
mGroupFlags |= FLAG_RUN_ANIMATION;
}
}
/**
* Returns the layout animation controller used to animate the group's
* children.
*
* @return the current animation controller
*/
public LayoutAnimationController getLayoutAnimation() {
return mLayoutAnimationController;
}
/**
* Indicates whether the children's drawing cache is used during a layout
* animation. By default, the drawing cache is enabled but this will prevent
* nested layout animations from working. To nest animations, you must disable
* the cache.
*
* @return true if the animation cache is enabled, false otherwise
*
* @see #setAnimationCacheEnabled(boolean)
* @see View#setDrawingCacheEnabled(boolean)
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
* Caching behavior of children may be controlled through {@link View#setLayerType(int, Paint)}.
*/
@Deprecated
public boolean isAnimationCacheEnabled() {
return (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
}
/**
* Enables or disables the children's drawing cache during a layout animation.
* By default, the drawing cache is enabled but this will prevent nested
* layout animations from working. To nest animations, you must disable the
* cache.
*
* @param enabled true to enable the animation cache, false otherwise
*
* @see #isAnimationCacheEnabled()
* @see View#setDrawingCacheEnabled(boolean)
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
* Caching behavior of children may be controlled through {@link View#setLayerType(int, Paint)}.
*/
@Deprecated
public void setAnimationCacheEnabled(boolean enabled) {
setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);
}
/**
* Indicates whether this ViewGroup will always try to draw its children using their
* drawing cache. By default this property is enabled.
*
* @return true if the animation cache is enabled, false otherwise
*
* @see #setAlwaysDrawnWithCacheEnabled(boolean)
* @see #setChildrenDrawnWithCacheEnabled(boolean)
* @see View#setDrawingCacheEnabled(boolean)
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
* Child views may no longer have their caching behavior disabled by parents.
*/
@Deprecated
public boolean isAlwaysDrawnWithCacheEnabled() {
return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE;
}
/**
* Indicates whether this ViewGroup will always try to draw its children using their
* drawing cache. This property can be set to true when the cache rendering is
* slightly different from the children's normal rendering. Renderings can be different,
* for instance, when the cache's quality is set to low.
*
* When this property is disabled, the ViewGroup will use the drawing cache of its
* children only when asked to. It's usually the task of subclasses to tell ViewGroup
* when to start using the drawing cache and when to stop using it.
*
* @param always true to always draw with the drawing cache, false otherwise
*
* @see #isAlwaysDrawnWithCacheEnabled()
* @see #setChildrenDrawnWithCacheEnabled(boolean)
* @see View#setDrawingCacheEnabled(boolean)
* @see View#setDrawingCacheQuality(int)
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
* Child views may no longer have their caching behavior disabled by parents.
*/
@Deprecated
public void setAlwaysDrawnWithCacheEnabled(boolean always) {
setBooleanFlag(FLAG_ALWAYS_DRAWN_WITH_CACHE, always);
}
/**
* Indicates whether the ViewGroup is currently drawing its children using
* their drawing cache.
*
* @return true if children should be drawn with their cache, false otherwise
*
* @see #setAlwaysDrawnWithCacheEnabled(boolean)
* @see #setChildrenDrawnWithCacheEnabled(boolean)
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
* Child views may no longer be forced to cache their rendering state by their parents.
* Use {@link View#setLayerType(int, Paint)} on individual Views instead.
*/
@Deprecated
protected boolean isChildrenDrawnWithCacheEnabled() {
return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE;
}
/**
* Tells the ViewGroup to draw its children using their drawing cache. This property
* is ignored when {@link #isAlwaysDrawnWithCacheEnabled()} is true. A child's drawing cache
* will be used only if it has been enabled.
*
* Subclasses should call this method to start and stop using the drawing cache when
* they perform performance sensitive operations, like scrolling or animating.
*
* @param enabled true if children should be drawn with their cache, false otherwise
*
* @see #setAlwaysDrawnWithCacheEnabled(boolean)
* @see #isChildrenDrawnWithCacheEnabled()
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
* Child views may no longer be forced to cache their rendering state by their parents.
* Use {@link View#setLayerType(int, Paint)} on individual Views instead.
*/
@Deprecated
protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
setBooleanFlag(FLAG_CHILDREN_DRAWN_WITH_CACHE, enabled);
}
/**
* Indicates whether the ViewGroup is drawing its children in the order defined by
* {@link #getChildDrawingOrder(int, int)}.
*
* @return true if children drawing order is defined by {@link #getChildDrawingOrder(int, int)},
* false otherwise
*
* @see #setChildrenDrawingOrderEnabled(boolean)
* @see #getChildDrawingOrder(int, int)
*/
@ViewDebug.ExportedProperty(category = "drawing")
protected boolean isChildrenDrawingOrderEnabled() {
return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
}
/**
* Tells the ViewGroup whether to draw its children in the order defined by the method
* {@link #getChildDrawingOrder(int, int)}.
*
* Note that {@link View#getZ() Z} reordering, done by {@link #dispatchDraw(Canvas)},
* will override custom child ordering done via this method.
*
* @param enabled true if the order of the children when drawing is determined by
* {@link #getChildDrawingOrder(int, int)}, false otherwise
*
* @see #isChildrenDrawingOrderEnabled()
* @see #getChildDrawingOrder(int, int)
*/
protected void setChildrenDrawingOrderEnabled(boolean enabled) {
setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
}
private boolean hasBooleanFlag(int flag) {
return (mGroupFlags & flag) == flag;
}
private void setBooleanFlag(int flag, boolean value) {
if (value) {
mGroupFlags |= flag;
} else {
mGroupFlags &= ~flag;
}
}
/**
* Returns an integer indicating what types of drawing caches are kept in memory.
*
* @see #setPersistentDrawingCache(int)
* @see #setAnimationCacheEnabled(boolean)
*
* @return one or a combination of {@link #PERSISTENT_NO_CACHE},
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
*
* @deprecated The view drawing cache was largely made obsolete with the introduction of
* hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
* layers are largely unnecessary and can easily result in a net loss in performance due to the
* cost of creating and updating the layer. In the rare cases where caching layers are useful,
* such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
* rendering. For software-rendered snapshots of a small part of the View hierarchy or
* individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
* {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
* software-rendered usages are discouraged and have compatibility issues with hardware-only
* rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
* bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
* reports or unit testing the {@link PixelCopy} API is recommended.
*/
@Deprecated
@ViewDebug.ExportedProperty(category = "drawing", mapping = {
@ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"),
@ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
@ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
@ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES, to = "ALL")
})
public int getPersistentDrawingCache() {
return mPersistentDrawingCache;
}
/**
* Indicates what types of drawing caches should be kept in memory after
* they have been created.
*
* @see #getPersistentDrawingCache()
* @see #setAnimationCacheEnabled(boolean)
*
* @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
*
* @deprecated The view drawing cache was largely made obsolete with the introduction of
* hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
* layers are largely unnecessary and can easily result in a net loss in performance due to the
* cost of creating and updating the layer. In the rare cases where caching layers are useful,
* such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
* rendering. For software-rendered snapshots of a small part of the View hierarchy or
* individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
* {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
* software-rendered usages are discouraged and have compatibility issues with hardware-only
* rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
* bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
* reports or unit testing the {@link PixelCopy} API is recommended.
*/
@Deprecated
public void setPersistentDrawingCache(int drawingCacheToKeep) {
mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
}
private void setLayoutMode(int layoutMode, boolean explicitly) {
mLayoutMode = layoutMode;
setBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET, explicitly);
}
/**
* Recursively traverse the view hierarchy, resetting the layoutMode of any
* descendants that had inherited a different layoutMode from a previous parent.
* Recursion terminates when a descendant's mode is:
*
* If no layoutMode was explicitly set, either programmatically or in an XML resource,
* the method returns the layoutMode of the view's parent ViewGroup if such a parent exists,
* otherwise the method returns a default value of {@link #LAYOUT_MODE_CLIP_BOUNDS}.
*
* @return the layout mode to use during layout operations
*
* @see #setLayoutMode(int)
*/
public int getLayoutMode() {
if (mLayoutMode == LAYOUT_MODE_UNDEFINED) {
int inheritedLayoutMode = (mParent instanceof ViewGroup) ?
((ViewGroup) mParent).getLayoutMode() : LAYOUT_MODE_DEFAULT;
setLayoutMode(inheritedLayoutMode, false);
}
return mLayoutMode;
}
/**
* Sets the basis of alignment during the layout of this ViewGroup.
* Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or
* {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*
* @param layoutMode the layout mode to use during layout operations
*
* @see #getLayoutMode()
* @attr ref android.R.styleable#ViewGroup_layoutMode
*/
public void setLayoutMode(int layoutMode) {
if (mLayoutMode != layoutMode) {
invalidateInheritedLayoutMode(layoutMode);
setLayoutMode(layoutMode, layoutMode != LAYOUT_MODE_UNDEFINED);
requestLayout();
}
}
/**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @param attrs the attributes to build the layout parameters from
*
* @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
/**
* Returns a safe set of layout parameters based on the supplied layout params.
* When a ViewGroup is passed a View whose layout params do not pass the test of
* {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
* is invoked. This method should return a new set of layout params suitable for
* this ViewGroup, possibly by copying the appropriate attributes from the
* specified set of layout params.
*
* @param p The layout parameters to convert into a suitable set of layout parameters
* for this ViewGroup.
*
* @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p;
}
/**
* Returns a set of default layout parameters. These parameters are requested
* when the View passed to {@link #addView(View)} has no layout parameters
* already set. If null is returned, an exception is thrown from addView.
*
* @return a set of default layout parameters or null
*/
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected void debug(int depth) {
super.debug(depth);
String output;
if (mFocused != null) {
output = debugIndent(depth);
output += "mFocused";
Log.d(VIEW_LOG_TAG, output);
mFocused.debug(depth + 1);
}
if (mDefaultFocus != null) {
output = debugIndent(depth);
output += "mDefaultFocus";
Log.d(VIEW_LOG_TAG, output);
mDefaultFocus.debug(depth + 1);
}
if (mFocusedInCluster != null) {
output = debugIndent(depth);
output += "mFocusedInCluster";
Log.d(VIEW_LOG_TAG, output);
mFocusedInCluster.debug(depth + 1);
}
if (mChildrenCount != 0) {
output = debugIndent(depth);
output += "{";
Log.d(VIEW_LOG_TAG, output);
}
int count = mChildrenCount;
for (int i = 0; i < count; i++) {
View child = mChildren[i];
child.debug(depth + 1);
}
if (mChildrenCount != 0) {
output = debugIndent(depth);
output += "}";
Log.d(VIEW_LOG_TAG, output);
}
}
/**
* Returns the position in the group of the specified child view.
*
* @param child the view for which to get the position
* @return a positive integer representing the position of the view in the
* group, or -1 if the view does not exist in the group
*/
public int indexOfChild(View child) {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
if (children[i] == child) {
return i;
}
}
return -1;
}
/**
* Returns the number of children in the group.
*
* @return a positive integer representing the number of children in
* the group
*/
public int getChildCount() {
return mChildrenCount;
}
/**
* 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];
}
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
/**
* Removes any pending animations for views that have been removed. Call
* this if you don't want animations for exiting views to stack up.
*/
public void clearDisappearingChildren() {
final ArrayList The default implementation of onStopNestedScroll calls
* {@link #stopNestedScroll()} to halt any recursive nested scrolling in progress. A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently
* acting as a nested scrolling parent for one or more descendant views in the hierarchy.
* The base LayoutParams class just describes how big the view wants to be
* for both width and height. For each dimension, it can specify one of:
* For more information about creating user interface layouts, read the
* XML Layouts developer
* guide. This function is called automatically when the developer setting is enabled. It is strongly advised to only call this function from debug builds as there is
* a risk of leaking unwanted layout information.super.onNestedPrePerformAccessibilityAction
*
*
*
*
*
* @param x hotspot x coordinate
* @param y hotspot y coordinate
* @see #drawableHotspotChanged(float, float)
*/
@Override
public void dispatchDrawableHotspotChanged(float x, float y) {
final int count = mChildrenCount;
if (count == 0) {
return;
}
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
// Children that are clickable on their own should not
// receive hotspots when their parent view does.
final boolean nonActionable = !child.isClickable() && !child.isLongClickable();
final boolean duplicatesState = (child.mViewFlags & DUPLICATE_PARENT_STATE) != 0;
if (nonActionable || duplicatesState) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
child.drawableHotspotChanged(point[0], point[1]);
}
}
}
@Override
void dispatchCancelPendingInputEvents() {
super.dispatchCancelPendingInputEvents();
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = 0; i < count; i++) {
children[i].dispatchCancelPendingInputEvents();
}
}
/**
* When this property is set to true, this ViewGroup supports static transformations on
* children; this causes
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
* invoked when a child is drawn.
*
* Any subclass overriding
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
* set this property to true.
*
* @param enabled True to enable static transformations on children, false otherwise.
*
* @see #getChildStaticTransformation(View, android.view.animation.Transformation)
*/
protected void setStaticTransformationsEnabled(boolean enabled) {
setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled);
}
/**
* Sets t
to be the static transformation of the child, if set, returning a
* boolean to indicate whether a static transform was set. The default implementation
* simply returns false
; subclasses may override this method for different
* behavior. {@link #setStaticTransformationsEnabled(boolean)} must be set to true
* for this method to be called.
*
* @param child The child view whose static transform is being requested
* @param t The Transformation which will hold the result
* @return true if the transformation was set, false otherwise
* @see #setStaticTransformationsEnabled(boolean)
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
return false;
}
Transformation getChildTransformation() {
if (mChildTransformation == null) {
mChildTransformation = new Transformation();
}
return mChildTransformation;
}
/**
* {@hide}
*/
@Override
protected null
means no transition will run on layout changes.
* @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
*/
public void setLayoutTransition(LayoutTransition transition) {
if (mTransition != null) {
LayoutTransition previousTransition = mTransition;
previousTransition.cancel();
previousTransition.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 null
means no transition will run on layout changes.
*/
public LayoutTransition getLayoutTransition() {
return mTransition;
}
private void removeViewsInternal(int start, int count) {
final int end = start + count;
if (start < 0 || count < 0 || end > mChildrenCount) {
throw new IndexOutOfBoundsException();
}
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
boolean clearChildFocus = false;
View clearDefaultFocus = null;
final View[] children = mChildren;
for (int i = start; i < end; i++) {
final View view = children[i];
if (mTransition != null) {
mTransition.removeChild(this, view);
}
if (view == focused) {
view.unFocus(null);
clearChildFocus = true;
}
if (view == mDefaultFocus) {
clearDefaultFocus = view;
}
if (view == mFocusedInCluster) {
clearFocusedInCluster(view);
}
view.clearAccessibilityFocus();
cancelTouchTarget(view);
cancelHoverTarget(view);
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
addDisappearingView(view);
} else if (detach) {
view.dispatchDetachedFromWindow();
}
if (view.hasTransientState()) {
childHasTransientStateChanged(view, false);
}
needGlobalAttributesUpdate(false);
dispatchViewRemoved(view);
}
removeFromArray(start, count);
if (clearDefaultFocus != null) {
clearDefaultFocus(clearDefaultFocus);
}
if (clearChildFocus) {
clearChildFocus(focused);
if (!rootViewRequestFocus()) {
notifyGlobalFocusCleared(focused);
}
}
}
/**
* Call this method to remove all child views from the
* ViewGroup.
*
*
*
* The first two clauses are optimizations.
* @param layoutModeOfRoot
*/
@Override
void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
if (mLayoutMode == LAYOUT_MODE_UNDEFINED ||
mLayoutMode == layoutModeOfRoot ||
hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
return;
}
setLayoutMode(LAYOUT_MODE_UNDEFINED, false);
// apply recursively
for (int i = 0, N = getChildCount(); i < N; i++) {
getChildAt(i).invalidateInheritedLayoutMode(layoutModeOfRoot);
}
}
/**
* Returns the basis of alignment during layout operations on this ViewGroup:
* either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*
*
* There are subclasses of LayoutParams for different subclasses of
* ViewGroup. For example, AbsoluteLayout has its own subclass of
* LayoutParams which adds an X and Y value.Developer Guides
*
*
*
* @param c the application environment
* @param attrs the set of attributes from which to extract the layout
* parameters' values
*/
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
/**
* Creates a new set of layout parameters with the specified width
* and height.
*
* @param width the width, either {@link #WRAP_CONTENT},
* {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
* API Level 8), or a fixed size in pixels
* @param height the height, either {@link #WRAP_CONTENT},
* {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
* API Level 8), or a fixed size in pixels
*/
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
/**
* Copy constructor. Clones the width and height values of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(LayoutParams source) {
this.width = source.width;
this.height = source.height;
}
/**
* Used internally by MarginLayoutParams.
* @hide
*/
LayoutParams() {
}
/**
* Extracts the layout parameters from the supplied attributes.
*
* @param a the style attributes to extract the parameters from
* @param widthAttr the identifier of the width attribute
* @param heightAttr the identifier of the height attribute
*/
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
/**
* Resolve layout parameters depending on the layout direction. Subclasses that care about
* layoutDirection changes should override this method. The default implementation does
* nothing.
*
* @param layoutDirection the direction of the layout
*
* {@link View#LAYOUT_DIRECTION_LTR}
* {@link View#LAYOUT_DIRECTION_RTL}
*/
public void resolveLayoutDirection(int layoutDirection) {
}
/**
* Returns a String representation of this set of layout parameters.
*
* @param output the String to prepend to the internal representation
* @return a String with the following format: output +
* "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }"
*
* @hide
*/
public String debug(String output) {
return output + "ViewGroup.LayoutParams={ width="
+ sizeToString(width) + ", height=" + sizeToString(height) + " }";
}
/**
* Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters.
*
* layout_width
: the width, either an exact value,
* {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
* {@link #MATCH_PARENT} in API Level 8)layout_height
: the height, either an exact value,
* {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
* {@link #MATCH_PARENT} in API Level 8)