Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d7dd8909 authored by Philip Milne's avatar Philip Milne
Browse files

New hooks to allow layouts to improve their performance by doing more caching

This change allows layouts to be notified of changes to LayoutParameters that have occurred
between layout operations.

If an assignment is made to the fields of LayoutParams instances that are already in use,
cachced data may become inconsistent with the new values. For complex layouts, like
GridLayout, in which the layout parameters define the structure of the layout, caching
could have caused  ArrayOutOfBoundsException to be raised without this change. This case is
rare in normal code as initialisation is typically performed once. Its nevertheless possible
and much more likely in environments like design tools where layout parametrs may be being
edited on the fly.

Prevent errors as follows (belt and braces):

1. Change javadoc to request that changes to the fields of LayoutParams be accompanied with
a call to View.setLayoutParams(). (This calls requestLayout() which was what the previous
javadoc advised.) Provide a (for now, private) hook for layouts with caches to receive notification
of such calls so they can invalidate any relevant internal state.

2. For GridLayout, we cannot clone layout parameters as traditional Java grids do without retaining
two complete copies because of the public getLayoutParameters() method on View. Retaining two
copies is wasteful on constrainted devices. Instead, we keep just one copy and compute a hashCode
for the critical fields of a GridLayout's layoutParams. The hashChode is checked it prior to all
layout operations; clearing the cache and logging a warning when changes are detected, so that
developers can fix their code to provide the call to setLayoutParams() as above.

Change-Id: I819ea65ec0ab82202e2f94fd5cd3ae2723c1a9a0
parent 07f941f0
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -8252,6 +8252,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        requestLayout();
    }
+16 −8
Original line number Diff line number Diff line
@@ -5203,6 +5203,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        return true;
    }

    /** @hide */
    protected void onSetLayoutParams(View child, LayoutParams layoutParams) {
    }

    /**
     * LayoutParams are used by views to tell their parents how they want to be
     * laid out. See
@@ -5411,29 +5415,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     */
    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
        /**
         * The left margin in pixels of the child. Whenever this value is changed, a call to
         * {@link android.view.View#requestLayout()} needs to be done.
         * The left margin in pixels of the child.
         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
         * to this field.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public int leftMargin;

        /**
         * The top margin in pixels of the child. Whenever this value is changed, a call to
         * {@link android.view.View#requestLayout()} needs to be done.
         * The top margin in pixels of the child.
         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
         * to this field.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public int topMargin;

        /**
         * The right margin in pixels of the child. Whenever this value is changed, a call to
         * {@link android.view.View#requestLayout()} needs to be done.
         * The right margin in pixels of the child.
         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
         * to this field.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public int rightMargin;

        /**
         * The bottom margin in pixels of the child. Whenever this value is changed, a call to
         * {@link android.view.View#requestLayout()} needs to be done.
         * The bottom margin in pixels of the child.
         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
         * to this field.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public int bottomMargin;
+74 −11
Original line number Diff line number Diff line
@@ -212,6 +212,7 @@ public class GridLayout extends ViewGroup {
    static final int PRF = 1;
    static final int MAX_SIZE = 100000;
    static final int DEFAULT_CONTAINER_MARGIN = 0;
    static final int UNINITIALIZED_HASH = 0;

    // Defaults

@@ -240,6 +241,7 @@ public class GridLayout extends ViewGroup {
    boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
    int alignmentMode = DEFAULT_ALIGNMENT_MODE;
    int defaultGap;
    int lastLayoutParamsHashCode = UNINITIALIZED_HASH;

    // Constructors

@@ -668,7 +670,7 @@ public class GridLayout extends ViewGroup {
        int[] maxSizes = new int[count];

        for (int i = 0, N = getChildCount(); i < N; i++) {
            LayoutParams lp = getLayoutParams1(getChildAt(i));
            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();

            final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
            final Interval majorRange = majorSpec.span;
@@ -713,6 +715,7 @@ public class GridLayout extends ViewGroup {

            minor = minor + minorSpan;
        }
        lastLayoutParamsHashCode = computeLayoutParamsHashCode();
        invalidateStructure();
    }

@@ -733,8 +736,11 @@ public class GridLayout extends ViewGroup {
        }
    }

    private LayoutParams getLayoutParams1(View c) {
        return (LayoutParams) c.getLayoutParams();
    /** @hide */
    @Override
    protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) {
        super.onSetLayoutParams(child, layoutParams);
        invalidateStructure();
    }

    final LayoutParams getLayoutParams(View c) {
@@ -742,7 +748,7 @@ public class GridLayout extends ViewGroup {
            validateLayoutParams();
            layoutParamsValid = true;
        }
        return getLayoutParams1(c);
        return (LayoutParams) c.getLayoutParams();
    }

    @Override
@@ -859,11 +865,28 @@ public class GridLayout extends ViewGroup {
        }
    }

    // Measurement
    private int computeLayoutParamsHashCode() {
        int result = 1;
        for (int i = 0, N = getChildCount(); i < N; i++) {
            View c = getChildAt(i);
            if (c.getVisibility() == View.GONE) continue;
            LayoutParams lp = (LayoutParams) c.getLayoutParams();
            result = 31 * result + lp.hashCode();
        }
        return result;
    }

    final boolean isGone(View c) {
        return c.getVisibility() == View.GONE;
    private void checkForLayoutParamsModification() {
        int layoutParamsHashCode = computeLayoutParamsHashCode();
        if (lastLayoutParamsHashCode != UNINITIALIZED_HASH &&
                lastLayoutParamsHashCode != layoutParamsHashCode) {
            invalidateStructure();
            Log.w(TAG, "The fields of some layout parameters were modified in between layout " +
                    "operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
        }
    }

    // Measurement

    private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
                                          int childWidth, int childHeight) {
@@ -877,7 +900,7 @@ public class GridLayout extends ViewGroup {
    private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
        for (int i = 0, N = getChildCount(); i < N; i++) {
            View c = getChildAt(i);
            if (isGone(c)) continue;
            if (c.getVisibility() == View.GONE) continue;
            LayoutParams lp = getLayoutParams(c);
            if (firstPass) {
                measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
@@ -902,6 +925,8 @@ public class GridLayout extends ViewGroup {

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        checkForLayoutParamsModification();

        /** If we have been called by {@link View#measure(int, int)}, one of width or height
         *  is  likely to have changed. We must invalidate if so. */
        invalidateValues();
@@ -941,7 +966,7 @@ public class GridLayout extends ViewGroup {
    }

    final int getMeasurementIncludingMargin(View c, boolean horizontal) {
        if (isGone(c)) {
        if (c.getVisibility() == View.GONE) {
            return 0;
        }
        return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
@@ -974,6 +999,8 @@ public class GridLayout extends ViewGroup {
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        checkForLayoutParamsModification();

        int targetWidth = right - left;
        int targetHeight = bottom - top;

@@ -990,7 +1017,7 @@ public class GridLayout extends ViewGroup {

        for (int i = 0, N = getChildCount(); i < N; i++) {
            View c = getChildAt(i);
            if (isGone(c)) continue;
            if (c.getVisibility() == View.GONE) continue;
            LayoutParams lp = getLayoutParams(c);
            Spec columnSpec = lp.columnSpec;
            Spec rowSpec = lp.rowSpec;
@@ -1527,7 +1554,7 @@ public class GridLayout extends ViewGroup {
            int[] margins = leading ? leadingMargins : trailingMargins;
            for (int i = 0, N = getChildCount(); i < N; i++) {
                View c = getChildAt(i);
                if (isGone(c)) continue;
                if (c.getVisibility() == View.GONE) continue;
                LayoutParams lp = getLayoutParams(c);
                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
                Interval span = spec.span;
@@ -1767,12 +1794,28 @@ public class GridLayout extends ViewGroup {
        /**
         * The spec that defines the vertical characteristics of the cell group
         * described by these layout parameters.
         * If an assignment is made to this field after a measurement or layout operation
         * has already taken place, a call to
         * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
         * must be made to notify GridLayout of the change. GridLayout is normally able
         * to detect when code fails to observe this rule, issue a warning and take steps to
         * compensate for the omission. This facility is implemented on a best effort basis
         * and should not be relied upon in production code - so it is best to include the above
         * calls to remove the warnings as soon as it is practical.
         */
        public Spec rowSpec = Spec.UNDEFINED;

        /**
         * The spec that defines the horizontal characteristics of the cell group
         * described by these layout parameters.
         * If an assignment is made to this field after a measurement or layout operation
         * has already taken place, a call to
         * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
         * must be made to notify GridLayout of the change. GridLayout is normally able
         * to detect when code fails to observe this rule, issue a warning and take steps to
         * compensate for the omission. This facility is implemented on a best effort basis
         * and should not be relied upon in production code - so it is best to include the above
         * calls to remove the warnings as soon as it is practical.
         */
        public Spec columnSpec = Spec.UNDEFINED;

@@ -1917,6 +1960,26 @@ public class GridLayout extends ViewGroup {
        final void setColumnSpecSpan(Interval span) {
            columnSpec = columnSpec.copyWriteSpan(span);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            LayoutParams that = (LayoutParams) o;

            if (!columnSpec.equals(that.columnSpec)) return false;
            if (!rowSpec.equals(that.rowSpec)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = rowSpec.hashCode();
            result = 31 * result + columnSpec.hashCode();
            return result;
        }
    }

    /*