OSDN Git Service

Add support for partial view layouts
authorAdam Powell <adamp@google.com>
Thu, 18 Dec 2014 23:22:26 +0000 (15:22 -0800)
committerAdam Powell <adamp@google.com>
Thu, 19 Nov 2015 17:31:56 +0000 (09:31 -0800)
Traditionally, when a view called requestLayout it would force
recursive requestLayout calls for all parent views up the
hierarchy. This meant that there was no way to determine at traversal
time whether a parent view itself needed layout, or if just one of its
descendants did.

Add a ViewParent method requestPartialLayoutForChild(View). This lets
a caller state that a particular child of a given parent needs a
remeasure and relayout at its current measured size and position
within that parent. This can help prevent the full-tree relayout often
caused by otherwise trivial changes. Partial layouts are processed
after any pending "full" relayout during ViewRoot traversals, but
before drawing.

Add a ViewGroup method requestLayoutForChild(View). This lets a
ViewGroup decide whether it is more appropriate to request a
traditional relayout or a partial layout for itself or just the child
that changed.

Add a ViewParent method findDependentLayoutAxes. This allows a caller
to check if the ViewParent's layout is dependent on a specific direct
child view along one or both axes. Called recursively, this can be
used to determine if a change in a child view can be isolated to a
partial layout, even if its direct parent's own layout is tied to its
other ancestors. (e.g. MATCH_PARENT, LinearLayout weights)

Implement ViewGroup#requestPartialLayoutForChild to call new
ViewParent method findDependentLayoutAxes and based on the result,
either request a full layout for itself or a partial layout for the
child in question.

Implement findDependentLayoutAxes for common framework ViewGroups. A
private implementation in ViewGroup is available for use by framework
classes that will deal with basic LayoutParams. These implementations
specifically check for derived LayoutParams classes and abort the
optimization if they find something beyond their expected parameter
types.

Change-Id: I0a1a9b79293d17d4fae8d9892b96d3586f9401ae

13 files changed:
api/current.txt
api/system-current.txt
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/java/android/view/ViewParent.java
core/java/android/view/ViewRootImpl.java
core/java/android/widget/AbsListView.java
core/java/android/widget/FrameLayout.java
core/java/android/widget/LinearLayout.java
core/java/android/widget/ListView.java
core/java/android/widget/TextView.java
core/java/android/widget/Toolbar.java
core/java/com/android/internal/widget/ActionBarOverlayLayout.java

index e17bf28..ba2a8a1 100644 (file)
@@ -36263,6 +36263,7 @@ package android.view {
     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);
@@ -36488,6 +36489,7 @@ package android.view {
     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();
@@ -37097,6 +37099,7 @@ package android.view {
     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);
@@ -37163,6 +37166,8 @@ package android.view {
     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();
@@ -37277,6 +37282,7 @@ package android.view {
     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);
@@ -37306,12 +37312,16 @@ package android.view {
     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 {
index d8b9b6f..3d925b7 100644 (file)
@@ -38584,6 +38584,7 @@ package android.view {
     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);
@@ -38809,6 +38810,7 @@ package android.view {
     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();
@@ -39418,6 +39420,7 @@ package android.view {
     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);
@@ -39484,6 +39487,8 @@ package android.view {
     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();
@@ -39598,6 +39603,7 @@ package android.view {
     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);
@@ -39627,12 +39633,16 @@ package android.view {
     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 {
index 30408c6..2415b4d 100644 (file)
@@ -83,6 +83,7 @@ import android.view.AccessibilityIterators.TextSegmentIterator;
 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;
@@ -2416,6 +2417,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      *                    1              PFLAG3_SCROLL_INDICATOR_END
      *                   1               PFLAG3_ASSIST_BLOCKED
      *            1111111                PFLAG3_POINTER_ICON_MASK
+     *           1                       PFLAG3_PARTIAL_LAYOUT_REQUESTED
+     *          1                        PFLAG3_LAYOUT_PARAMS_CHANGED
      * |-------|-------|-------|-------|
      */
 
@@ -2504,6 +2507,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      */
     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;
@@ -2642,6 +2646,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     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.
      *
@@ -12622,10 +12639,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * 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
@@ -12642,6 +12663,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * 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) {
@@ -12649,6 +12673,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             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);
@@ -14324,7 +14349,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             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();
 
@@ -14662,8 +14692,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      */
     @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();
@@ -16850,6 +16885,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     }
 
     /**
+     * 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
      */
@@ -16906,6 +16964,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         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) {
@@ -16919,6 +16978,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         }
 
         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+        mPrivateFlags3 &= ~PFLAG3_PARTIAL_LAYOUT_REQUESTED;
         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
     }
 
@@ -19012,7 +19072,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         mPrivateFlags |= PFLAG_INVALIDATED;
 
         if (mParent != null && !mParent.isLayoutRequested()) {
-            mParent.requestLayout();
+            mParent.requestLayoutForChild(this);
         }
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
             mAttachInfo.mViewRequestingLayout = null;
@@ -19031,6 +19091,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         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
@@ -21867,6 +21932,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         interface Callbacks {
             void playSoundEffect(int effectId);
             boolean performHapticFeedback(int effectId, boolean always);
+            void schedulePartialLayout();
         }
 
         /**
@@ -22242,6 +22308,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         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.
          *
index 25df004..6812fd1 100644 (file)
@@ -60,6 +60,8 @@ import java.util.HashSet;
 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>
@@ -5517,6 +5519,172 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
             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.
      *
@@ -5862,7 +6030,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
      *         of its descendants
      */
     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return p;
+        return new LayoutParams(p);
     }
 
     /**
index 07f1e2c..6ae448a 100644 (file)
@@ -26,6 +26,11 @@ import android.view.accessibility.AccessibilityEvent;
  * 
  */
 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
@@ -601,4 +606,48 @@ public interface ViewParent {
      * @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);
 }
index f1d9f1a..2c0cd7a 100644 (file)
@@ -91,6 +91,7 @@ import java.lang.ref.WeakReference;
 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
@@ -952,6 +953,25 @@ public final class ViewRootImpl implements ViewParent,
     }
 
     @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;
     }
@@ -1095,6 +1115,10 @@ public final class ViewRootImpl implements ViewParent,
         }
     }
 
+    public void schedulePartialLayout() {
+        scheduleTraversals();
+    }
+
     /**
      * Notifies the HardwareRenderer that a new frame will be coming soon.
      * Currently only {@link ThreadedRenderer} cares about this, and uses
@@ -1934,7 +1958,48 @@ public final class ViewRootImpl implements ViewParent,
                 || 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
 
@@ -1964,7 +2029,7 @@ public final class ViewRootImpl implements ViewParent,
 
             if (DBG) {
                 System.out.println("======================================");
-                System.out.println("performTraversals -- after setFrame");
+                System.out.println("performTraversals -- after performLayout/partial layout");
                 host.debug();
             }
         }
index b8faf0c..90de053 100644 (file)
@@ -2109,6 +2109,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
     }
 
     @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();
index 280ff15..4d9f55c 100644 (file)
@@ -21,12 +21,8 @@ import java.util.ArrayList;
 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;
@@ -36,9 +32,6 @@ import android.view.ViewGroup;
 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
@@ -171,6 +164,10 @@ public class FrameLayout extends ViewGroup {
             mPaddingBottom + mForegroundPaddingBottom;
     }
 
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
 
     /**
      * {@inheritDoc}
index ad939be..ba868a1 100644 (file)
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.view.ViewParent;
 import com.android.internal.R;
 
 import android.annotation.IntDef;
@@ -37,6 +38,8 @@ import android.widget.RemoteViews.RemoteView;
 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 
@@ -644,6 +647,60 @@ public class LinearLayout extends ViewGroup {
         }
     }
 
+    @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.
      *
index 53ca6d1..b43ea76 100644 (file)
@@ -1158,7 +1158,7 @@ public class ListView extends AbsListView {
             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();
index 3e6d121..eaf4fe2 100644 (file)
@@ -6801,10 +6801,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
 
         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())
@@ -7200,37 +7201,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
      * 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();
     }
 
     /**
@@ -7238,56 +7211,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
      * 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
index acbf5eb..6e56513 100644 (file)
@@ -1368,6 +1368,11 @@ public class Toolbar extends ViewGroup {
     }
 
     @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;
index c3a7460..3e65320 100644 (file)
@@ -360,6 +360,11 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         pullChildren();