method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
+ method public final boolean didLayoutParamsChange();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
method public boolean isPaddingRelative();
+ method public final boolean isPartialLayoutRequested();
method public boolean isPressed();
method public boolean isSaveEnabled();
method public boolean isSaveFromParentEnabled();
method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
method public void endViewTransition(android.view.View);
+ method public int findDependentLayoutAxes(android.view.View, int);
method public android.view.View focusSearch(android.view.View, int);
method public void focusableViewAvailable(android.view.View);
method public boolean gatherTransparentRegion(android.graphics.Region);
method public void requestChildFocus(android.view.View, android.view.View);
method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
method public void requestDisallowInterceptTouchEvent(boolean);
+ method public void requestLayoutForChild(android.view.View);
+ method public void requestPartialLayoutForChild(android.view.View);
method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method public void scheduleLayoutAnimation();
method public abstract void childHasTransientStateChanged(android.view.View, boolean);
method public abstract void clearChildFocus(android.view.View);
method public abstract void createContextMenu(android.view.ContextMenu);
+ method public abstract int findDependentLayoutAxes(android.view.View, int);
method public abstract android.view.View focusSearch(android.view.View, int);
method public abstract void focusableViewAvailable(android.view.View);
method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
method public abstract void requestDisallowInterceptTouchEvent(boolean);
method public abstract void requestFitSystemWindows();
method public abstract void requestLayout();
+ method public abstract void requestLayoutForChild(android.view.View);
method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public abstract void requestTransparentRegion(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View, float, float);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+ field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+ field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
}
public class ViewPropertyAnimator {
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
+ method public final boolean didLayoutParamsChange();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
method public boolean isPaddingRelative();
+ method public final boolean isPartialLayoutRequested();
method public boolean isPressed();
method public boolean isSaveEnabled();
method public boolean isSaveFromParentEnabled();
method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
method public void endViewTransition(android.view.View);
+ method public int findDependentLayoutAxes(android.view.View, int);
method public android.view.View focusSearch(android.view.View, int);
method public void focusableViewAvailable(android.view.View);
method public boolean gatherTransparentRegion(android.graphics.Region);
method public void requestChildFocus(android.view.View, android.view.View);
method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
method public void requestDisallowInterceptTouchEvent(boolean);
+ method public void requestLayoutForChild(android.view.View);
+ method public void requestPartialLayoutForChild(android.view.View);
method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method public void scheduleLayoutAnimation();
method public abstract void childHasTransientStateChanged(android.view.View, boolean);
method public abstract void clearChildFocus(android.view.View);
method public abstract void createContextMenu(android.view.ContextMenu);
+ method public abstract int findDependentLayoutAxes(android.view.View, int);
method public abstract android.view.View focusSearch(android.view.View, int);
method public abstract void focusableViewAvailable(android.view.View);
method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
method public abstract void requestDisallowInterceptTouchEvent(boolean);
method public abstract void requestFitSystemWindows();
method public abstract void requestLayout();
+ method public abstract void requestLayoutForChild(android.view.View);
method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public abstract void requestTransparentRegion(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View, float, float);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+ field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+ field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
}
public class ViewPropertyAnimator {
import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
+import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
* 1111111 PFLAG3_POINTER_ICON_MASK
+ * 1 PFLAG3_PARTIAL_LAYOUT_REQUESTED
+ * 1 PFLAG3_LAYOUT_PARAMS_CHANGED
* |-------|-------|-------|-------|
*/
*/
static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
+
/* End of masks for mPrivateFlags3 */
static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
/**
+ * Flag indicating that this view has requested a partial layout and
+ * is added to the AttachInfo's list of views that need a partial layout
+ * request handled on the next traversal.
+ */
+ static final int PFLAG3_PARTIAL_LAYOUT_REQUESTED = 0x800000;
+
+ /**
+ * Flag indicating that this view's LayoutParams have been explicitly changed
+ * since the last layout pass.
+ */
+ static final int PFLAG3_LAYOUT_PARAMS_CHANGED = 0x1000000;
+
+ /**
* Always allow a user to over-scroll this view, provided it is a
* view that can scroll.
*
* ViewGroup.LayoutParams, and these correspond to the different subclasses
* of ViewGroup that are responsible for arranging their children.
*
- * This method may return null if this View is not attached to a parent
+ * <p>This method may return null if this View is not attached to a parent
* ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)}
* was not invoked successfully. When a View is attached to a parent
- * ViewGroup, this method must not return null.
+ * ViewGroup, this method must not return null.</p>
+ *
+ * <p>Callers that modify the returned LayoutParams object should call
+ * {@link #setLayoutParams(LayoutParams)} to explicitly inform the view that
+ * LayoutParams have changed.</p>
*
* @return The LayoutParams associated with this view, or null if no
* parameters have been set yet
* correspond to the different subclasses of ViewGroup that are responsible
* for arranging their children.
*
+ * <p>If the View's existing LayoutParams object as obtained by {@link #getLayoutParams()} is
+ * modified, you should call this method to inform the view that it has changed.</p>
+ *
* @param params The layout parameters for this view, cannot be null
*/
public void setLayoutParams(ViewGroup.LayoutParams params) {
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
+ mPrivateFlags3 |= PFLAG3_LAYOUT_PARAMS_CHANGED;
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
mParent.requestTransparentRegion(this);
}
- mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
+ initialAwakenScrollBars();
+ mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
+ }
+
+ mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED);
jumpDrawablesToCurrentState();
*/
@CallSuper
protected void onDetachedFromWindowInternal() {
+ if (mAttachInfo != null && isPartialLayoutRequested()) {
+ mAttachInfo.mPartialLayoutViews.remove(this);
+ }
+
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
- mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED
+ | PFLAG3_LAYOUT_PARAMS_CHANGED);
removeUnsetPressCallback();
removeLongPressCallback();
}
/**
+ * Indicates whether or not this view has requested a partial layout that
+ * may not affect its size or position within its parent. This state will be reset
+ * the next time this view is laid out.
+ *
+ * @return true if partial layout has been requested
+ */
+ public final boolean isPartialLayoutRequested() {
+ return (mPrivateFlags3 & PFLAG3_PARTIAL_LAYOUT_REQUESTED)
+ == PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+ }
+
+ /**
+ * Returns true if this view's {@link ViewGroup.LayoutParams LayoutParams} changed
+ * since the last time this view was successfully laid out. Typically this happens as a
+ * result of a call to {@link #setLayoutParams(LayoutParams)}.
+ *
+ * @return true if this view's LayoutParams changed since last layout.
+ */
+ public final boolean didLayoutParamsChange() {
+ return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED;
+ }
+
+ /**
* Return true if o is a ViewGroup that is laying out using optical bounds.
* @hide
*/
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
+ mPrivateFlags3 &= ~PFLAG3_LAYOUT_PARAMS_CHANGED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+ mPrivateFlags3 &= ~PFLAG3_PARTIAL_LAYOUT_REQUESTED;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
- mParent.requestLayout();
+ mParent.requestLayoutForChild(this);
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
mPrivateFlags |= PFLAG_INVALIDATED;
}
+ void forcePartialLayout() {
+ forceLayout();
+ mPrivateFlags3 |= PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+ }
+
/**
* <p>
* This is called to find out how big a view should be. The parent
interface Callbacks {
void playSoundEffect(int effectId);
boolean performHapticFeedback(int effectId, boolean always);
+ void schedulePartialLayout();
}
/**
IBinder mDragToken;
/**
+ * Used to track views that need (at least) a partial relayout at their current size
+ * during the next traversal.
+ */
+ final List<View> mPartialLayoutViews = new ArrayList<View>();
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
import java.util.List;
import java.util.Map;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* <p>
int l, int t, int r, int b);
/**
+ * {@inheritDoc}
+ *
+ * <p>Most subclasses should not need to override this method. The default implementation
+ * will call {@link #findDependentLayoutAxes(View, int)} to determine how
+ * to optimally proceed. If neither horizontal nor vertical layout depends on the given
+ * child, this method will call {@link #requestPartialLayoutForChild(View)}. If one or both
+ * do, it will call {@link #requestLayout()}.</p>
+ *
+ * @param child Child requesting a layout
+ */
+ @Override
+ public void requestLayoutForChild(View child) {
+ if (child == null || child.getParent() != this) {
+ throw new IllegalArgumentException(
+ "child parameter must be a direct child view of this ViewGroup");
+ }
+
+ // If we don't have a parent ourselves, record that we need a full layout.
+ // Our whole subtree is detached.
+ final ViewParent parent = getParent();
+ if (parent == null) {
+ requestLayout();
+ return;
+ }
+
+ // We can optimize the layout request for this child into a partial layout
+ // if the child has already been laid out at least once and neither horizontal nor
+ // vertical layout within ourselves is dependent on pending layout changes within
+ // this child. Otherwise we need to request a full layout for ourselves and continue
+ // to recurse up the view hierarchy.
+ if (child.isLaidOut() && findDependentLayoutAxes(child, FLAG_LAYOUT_AXIS_ANY) == 0) {
+ requestPartialLayoutForChild(child);
+ } else {
+ requestLayout();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns {@link #FLAG_LAYOUT_AXIS_ANY}.
+ * Optimized implementations for specific ViewGroup subclasses may check if the child's
+ * {@link View#didLayoutParamsChange() LayoutParams changed} and in what ways.</p>
+ *
+ * @param child Direct child of this ViewParent to check
+ * @param axisFilter Which axes to check for dependencies. Can be
+ * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+ * or {@link #FLAG_LAYOUT_AXIS_ANY}.
+ * @return Axes of this ViewParent that depend on the given child's layout changes
+ */
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return FLAG_LAYOUT_AXIS_ANY;
+ }
+
+ /**
+ * This is a helper implementation for {@link #findDependentLayoutAxes(View, int)} that
+ * is not the default implementation in ViewGroup. This is to preserve compatibility with
+ * existing app-side ViewGroup subclasses that existed before the partial layout system was
+ * added to Android. It explicitly checks that the LayoutParams of the child are of the
+ * expected type so that subclasses of standard framework layouts do not erroneously
+ * start believing that it's safe to do a partial layout when that assertion can't
+ * reasonably be confirmed.
+ *
+ * <p>If you're reading this as an author of a custom ViewGroup's findDependentLayoutAxes
+ * method you might be frustrated to discover that it is not a part of the Android public API.
+ * Many ViewGroup implementations will need to make small but important modifications
+ * to an implementation like this one in order to be correct. Instead of encouraging
+ * view authors to call this method, then make their own redundant recursive calls to
+ * <code>getParent().findDependentLayoutAxes(...)</code> in addition to the one
+ * that can happen here, this method is hidden and only used internally.</p>
+ *
+ * <p>Do feel free to copy this implementation and adapt it to suit your own purposes.</p>
+ *
+ * @hide
+ */
+ protected final int findDependentLayoutAxesHelper(View child, int axisFilter,
+ Class<?> layoutParamsClass) {
+ if (!checkPartialLayoutParams(child, layoutParamsClass)) return axisFilter;
+ if (child.didLayoutParamsChange()) {
+ // Anything could have changed about our previous assumptions.
+ return axisFilter;
+ }
+
+ final LayoutParams lp = child.getLayoutParams();
+
+ // Our layout can always end up depending on a WRAP_CONTENT child.
+ final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ if (wrapAxisFilter == axisFilter) {
+ // We know all queried axes are affected, just return early.
+ return wrapAxisFilter;
+ }
+
+ // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+ // that our layout will remain stable within our parent. We need to ask.
+ final int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ if (matchAxisFilter != 0) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ // If our parent depends on us for an axis, then our layout can also be affected
+ // by a MATCH_PARENT child along that axis.
+ return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+ | wrapAxisFilter;
+ }
+
+ // If we don't have a parent, assume we're affected
+ // in any determined affected direction.
+ return matchAxisFilter | wrapAxisFilter;
+ }
+
+ // Two exact sizes and LayoutParams didn't change. We're safe.
+ return 0;
+ }
+
+ /**
+ * Throw an IllegalArgumentException if the supplied view is not a direct child of
+ * this ViewGroup and return false if this view's LayoutParams is not of class lpClass.
+ * Implementations of {@link ViewGroup#findDependentLayoutAxes(View, int)} use this
+ * to check input parameters and defensively return the full axis filter mask themselves
+ * if the LayoutParams class is not of the exact expected type; e.g. it is a subclass
+ * of one of the standard framework layouts and we can't make assumptions.
+ * @hide
+ */
+ protected final boolean checkPartialLayoutParams(View child, Class<?> lpClass) {
+ if (child.getParent() != this) {
+ throw new IllegalArgumentException("View " + child
+ + " is not a direct child of " + this);
+ }
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ return lp != null || lp.getClass() == lpClass;
+ }
+
+ /**
+ * Called when a child of this ViewParent requires a relayout before the next frame
+ * is drawn, but the caller can guarantee that the size of the child will not change
+ * during a measure and layout pass.
+ *
+ * <p>A call to this method will schedule a partial layout for the supplied view as long as
+ * it is a direct child of this ViewGroup and this ViewGroup is attached to a window.
+ * On the next scheduled view hierarchy traversal the given child view will be re-measured
+ * at its current measured size and re-laid out at its current position within its parent.</p>
+ *
+ * @param child Child that requires a partial layout
+ */
+ public void requestPartialLayoutForChild(View child) {
+ if (!child.isPartialLayoutRequested()) {
+ child.forcePartialLayout();
+ if (mAttachInfo != null) {
+ final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+ final boolean schedule = partialLayoutViews.isEmpty();
+ partialLayoutViews.add(child);
+ if (schedule) {
+ mAttachInfo.mRootCallbacks.schedulePartialLayout();
+ }
+ child.invalidate();
+ } else {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
* Indicates whether the view group has the ability to animate its children
* after the first layout.
*
* of its descendants
*/
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return p;
+ return new LayoutParams(p);
}
/**
*
*/
public interface ViewParent {
+ public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1;
+ public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2;
+ public static final int FLAG_LAYOUT_AXIS_ANY
+ = FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL;
+
/**
* 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
* @return true if the action was consumed by this ViewParent
*/
public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+
+ /**
+ * Called when a child of this ViewParent requires a relayout before
+ * the next frame is drawn. A call to {@link View#requestLayout() child.requestLayout()}
+ * will implicitly result in a call to
+ * <code>child.getParent().requestLayoutForChild(child)</code>. App code should not call this
+ * method directly. Call <code>child.requestLayout()</code> instead.
+ *
+ * <p>On versions of Android from API 23 and older, a call to {@link View#requestLayout()}
+ * would cause a matching call to <code>requestLayout</code> on each parent view up to
+ * the root. With the addition of <code>requestLayoutForChild</code> a view's parent may
+ * explicitly decide how to handle a layout request. This allows for optimizations when
+ * a view parent knows that a layout-altering change in a child will not affect its own
+ * measurement.</p>
+ *
+ * @param child Child requesting a layout
+ */
+ public void requestLayoutForChild(View child);
+
+ /**
+ * Determine which axes of this ViewParent's layout are dependent on the given
+ * direct child view. The returned value is a flag set that may contain
+ * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL} and/or {@link #FLAG_LAYOUT_AXIS_VERTICAL}.
+ * {@link #FLAG_LAYOUT_AXIS_ANY} is provided as a shortcut for
+ * <code>FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL</code>.
+ *
+ * <p>The given child must be a direct child view. Implementations should throw
+ * {@link IllegalArgumentException} otherwise.</p>
+ *
+ * <p>The caller may specify which axes it cares about. This should be treated as a filter.
+ * Implementations should never return a result that would be different from
+ * <code>result & axisFilter</code>.</p>
+ *
+ * @param child Direct child of this ViewParent to check
+ * @param axisFilter Which axes to check for dependencies. Can be
+ * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+ * or {@link #FLAG_LAYOUT_AXIS_ANY}.
+ * @return Axes of this ViewParent that depend on the given child's layout changes
+ *
+ * @see #FLAG_LAYOUT_AXIS_HORIZONTAL
+ * @see #FLAG_LAYOUT_AXIS_VERTICAL
+ * @see #FLAG_LAYOUT_AXIS_ANY
+ */
+ public int findDependentLayoutAxes(View child, int axisFilter);
}
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.HashSet;
+import java.util.List;
/**
* The top of a view hierarchy, implementing the needed protocol between View
}
@Override
+ public void requestLayoutForChild(View child) {
+ requestLayout();
+ }
+
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ if (child != mView) {
+ return 0;
+ }
+
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) child.getLayoutParams();
+ final int horizontal = (lp.width == WindowManager.LayoutParams.WRAP_CONTENT
+ || lp.horizontalWeight != 0) ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0;
+ final int vertical = (lp.height == WindowManager.LayoutParams.WRAP_CONTENT
+ || lp.verticalWeight != 0) ? FLAG_LAYOUT_AXIS_VERTICAL : 0;
+ return (horizontal | vertical) & axisFilter;
+ }
+
+ @Override
public boolean isLayoutRequested() {
return mLayoutRequested;
}
}
}
+ public void schedulePartialLayout() {
+ scheduleTraversals();
+ }
+
/**
* Notifies the HardwareRenderer that a new frame will be coming soon.
* Currently only {@link ThreadedRenderer} cares about this, and uses
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
+ }
+
+ /*
+ * Handle partial layouts.
+ *
+ * Views that have requested partial layouts will not change size or position
+ * within their parent view, therefore we will re-measure and re-layout each one
+ * after any regularly scheduled layout pass. Any view that already had its
+ * isLayoutRequested bit cleared will be skipped, since this means the view has already
+ * been measured and laid out on this traversal pass naturally. Views won't be added
+ * to this list if layout was already requested when a partial layout is requested
+ * for a view, so there should not be duplicates in the list.
+ */
+ final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+ final boolean didPartialLayout;
+ if (!partialLayoutViews.isEmpty()) {
+ final int count = partialLayoutViews.size();
+ mInLayout = true;
+ for (int i = 0; i < count; i++) {
+ final View view = partialLayoutViews.get(i);
+
+ // Make sure the view is still attached and that it still has layout requested.
+ // We might have already serviced the layout request through the standard full-tree
+ // layout pass above or even through a previous partial layout view in this list.
+ if (view.isAttachedToWindow() && view.isLayoutRequested()) {
+ final int widthSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
+ MeasureSpec.EXACTLY);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
+ MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+ }
+ }
+ mInLayout = false;
+ partialLayoutViews.clear();
+ didPartialLayout = true;
+ triggerGlobalLayoutListener = true;
+ } else {
+ didPartialLayout = false;
+ }
+ if (didLayout || didPartialLayout) {
// By this point all views have been sized and positioned
// We can compute the transparent area
if (DBG) {
System.out.println("======================================");
- System.out.println("performTraversals -- after setFrame");
+ System.out.println("performTraversals -- after performLayout/partial layout");
host.debug();
}
}
}
@Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mSelector == null) {
useDefaultSelector();
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
-
/**
* FrameLayout is designed to block out an area on the screen to display
* a single item. Generally, FrameLayout should be used to hold a single child view, because it can
mPaddingBottom + mForegroundPaddingBottom;
}
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
/**
* {@inheritDoc}
package android.widget;
+import android.view.ViewParent;
import com.android.internal.R;
import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* A Layout that arranges its children in a single column or a single row. The direction of
}
}
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ // This implementation is almost exactly equivalent to the default implementation
+ // offered to the rest of the framework in ViewGroup, but we treat weight to be
+ // functionally equivalent to MATCH_PARENT along the orientation axis.
+
+ if (!checkPartialLayoutParams(child, LayoutParams.class)) return axisFilter;
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child.didLayoutParamsChange()) {
+ // Anything could have changed about our previous assumptions.
+ return axisFilter;
+ }
+
+ // Our layout can always end up depending on a WRAP_CONTENT child.
+ final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ if (wrapAxisFilter == axisFilter) {
+ // We know all queried axes are affected, just return early.
+ return wrapAxisFilter;
+ }
+
+ // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+ // that our layout will remain stable within our parent. We need to ask.
+ int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ // For LinearLayout, a nonzero weight is equivalent to MATCH_PARENT for this purpose.
+ if (lp.weight > 0) {
+ if (mOrientation == HORIZONTAL) {
+ matchAxisFilter |= FLAG_LAYOUT_AXIS_HORIZONTAL & axisFilter;
+ } else {
+ matchAxisFilter |= FLAG_LAYOUT_AXIS_VERTICAL & axisFilter;
+ }
+ }
+
+ if (matchAxisFilter != 0) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ // If our parent depends on us for an axis, then our layout can also be affected
+ // by a MATCH_PARENT child along that axis.
+ return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+ | wrapAxisFilter;
+ }
+
+ // If we don't have a parent, assume we're affected
+ // in any determined affected direction.
+ return matchAxisFilter | wrapAxisFilter;
+ }
+
+ // Two exact sizes and LayoutParams didn't change. We're safe.
+ return 0;
+ }
+
/**
* Determines where to position dividers between children.
*
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
- // we can obtain exected minimum width and height.
+ // we can obtain expected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
if (!compressText(ellipsisWidth)) {
- final int height = mLayoutParams.height;
// If the size of the view does not depend on the size of the text, try to
// start the marquee immediately
- if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
+ final ViewParent parent = getParent();
+ if (parent != null && parent.findDependentLayoutAxes(this,
+ ViewParent.FLAG_LAYOUT_AXIS_VERTICAL) == 0) {
startMarquee();
} else {
// Defer the start of the marquee until we know our width (see setFrame())
* new view layout.
*/
private void checkForResize() {
- boolean sizeChanged = false;
-
- if (mLayout != null) {
- // Check if our width changed
- if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
- sizeChanged = true;
- invalidate();
- }
-
- // Check if our height changed
- if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
- int desiredHeight = getDesiredHeight();
-
- if (desiredHeight != this.getHeight()) {
- sizeChanged = true;
- }
- } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
- if (mDesiredHeightAtMeasure >= 0) {
- int desiredHeight = getDesiredHeight();
-
- if (desiredHeight != mDesiredHeightAtMeasure) {
- sizeChanged = true;
- }
- }
- }
- }
-
- if (sizeChanged) {
- requestLayout();
- // caller will have already invalidated
- }
+ // Always request a layout. The parent will perform the correct version
+ // of the intended optimizations as part of requestLayoutForChild.
+ requestLayout();
}
/**
* or merely a new text layout.
*/
private void checkForRelayout() {
- // If we have a fixed width, we can just swap in a new text layout
- // if the text height stays the same or if the view height is fixed.
-
- if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
- (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
- (mHint == null || mHintLayout != null) &&
- (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
- // Static width, so try making a new text layout.
-
- int oldht = mLayout.getHeight();
- int want = mLayout.getWidth();
- int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
-
- /*
- * No need to bring the text into view, since the size is not
- * changing (unless we do the requestLayout(), in which case it
- * will happen at measure).
- */
- makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
- mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
- false);
-
- if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
- // In a fixed-height view, so use our new text layout.
- if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
- mLayoutParams.height != LayoutParams.MATCH_PARENT) {
- invalidate();
- return;
- }
-
- // Dynamic height, but height has stayed the same,
- // so use our new text layout.
- if (mLayout.getHeight() == oldht &&
- (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
- invalidate();
- return;
- }
- }
-
- // We lose: the height has changed and we have a dynamic height.
- // Request a new view layout using our new text layout.
- requestLayout();
- invalidate();
- } else {
- // Dynamic width, so we have no choice but to request a new
- // view layout with a new text layout.
- nullLayouts();
- requestLayout();
- invalidate();
- }
+ // Always request a layout. The parent will perform the correct version
+ // of the intended optimizations as part of requestLayoutForChild.
+ nullLayouts();
+ requestLayout();
}
@Override
}
@Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
}
@Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
pullChildren();