Port weight feature from L into v7 GridLayout support library

We think the weight feature introduced into GridLayout in L 
leaves GridLayout with a superset of the facilities of the 
other layouts in the Android platform. This CL updates the v7 
version of GridLayout in the support library to the version 
in L. 

Change-Id: I339d6b62cc0eed666f66a250c8ff23c74ec78784
diff --git a/v7/gridlayout/res/values/attrs.xml b/v7/gridlayout/res/values/attrs.xml
index ab3bc23..90dc702 100644
--- a/v7/gridlayout/res/values/attrs.xml
+++ b/v7/gridlayout/res/values/attrs.xml
@@ -124,6 +124,9 @@
         See {@link android.widget.GridLayout.Spec}.
         -->
         <attr name="layout_rowSpan" format="integer" min="1" />
+        <!-- The relative proportion of horizontal space that should be allocated to this view
+        during excess space distribution. -->
+        <attr name="layout_rowWeight" format="float" />
         <!--
         The column boundary delimiting the left of the group of cells
         occupied by this view.
@@ -136,6 +139,9 @@
         See {@link android.widget.GridLayout.Spec}.
         -->
         <attr name="layout_columnSpan" format="integer" min="1" />
+        <!-- The relative proportion of vertical space that should be allocated to this view
+        during excess space distribution. -->
+        <attr name="layout_columnWeight" format="float" />
         <!--
         Gravity specifies how a component should be placed in its group of cells.
         The default is LEFT | BASELINE.
diff --git a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
index a0e6ca7..b5cb092 100644
--- a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
+++ b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
@@ -102,14 +102,15 @@
  *
  * <h4>Excess Space Distribution</h4>
  *
- * GridLayout's distribution of excess space is based on <em>priority</em>
- * rather than <em>weight</em>.
+ * GridLayout's distribution of excess space accommodates the principle of weight.
+ * In the event that no weights are specified, columns and rows are taken as
+ * flexible if their views specify some form of alignment within their groups.
  * <p>
- * A child's ability to stretch is inferred from the alignment properties of
- * its row and column groups (which are typically set by setting the
- * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
- * If alignment was defined along a given axis then the component
- * is taken as <em>flexible</em> in that direction. If no alignment was set,
+ * The flexibility of a view is therefore influenced by its alignment which is,
+ * in turn, typically defined by setting the
+ * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
+ * If either a weight or alignment were defined along a given axis then the component
+ * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
  * the component is instead assumed to be <em>inflexible</em>.
  * <p>
  * Multiple components in the same row or column group are
@@ -120,12 +121,16 @@
  * elements is flexible if <em>one</em> of its elements is flexible.
  * <p>
  * To make a column stretch, make sure all of the components inside it define a
- * gravity. To prevent a column from stretching, ensure that one of the components
- * in the column does not define a gravity.
+ * weight or a gravity. To prevent a column from stretching, ensure that one of the components
+ * in the column does not define a weight or a gravity.
  * <p>
  * When the principle of flexibility does not provide complete disambiguation,
  * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
- * and <em>bottom</em> edges.
+ * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
+ * parameters as a constraint in the a set of variables that define the grid-lines along a
+ * given axis. During layout, GridLayout solves the constraints so as to return the unique
+ * solution to those constraints for which all variables are less-than-or-equal-to
+ * the corresponding value in any other valid solution.
  *
  * <h4>Interpretation of GONE</h4>
  *
@@ -138,18 +143,6 @@
  * had never been added to it.
  * These statements apply equally to rows as well as columns, and to groups of rows or columns.
  *
- * <h5>Limitations</h5>
- *
- * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
- * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
- * to configure a GridLayout to distribute excess space between multiple components.
- * <p>
- * Some common use-cases may nevertheless be accommodated as follows.
- * To place equal amounts of space around a component in a cell group;
- * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
- * For complete control over excess space distribution in a row or column;
- * use a {@link LinearLayout} subview to hold the components in the associated cell group.
- * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
  * <p>
  * See {@link GridLayout.LayoutParams} for a full description of the
  * layout parameters used by GridLayout.
@@ -161,7 +154,7 @@
  * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
  * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
  */
-public class GridLayout extends android.view.ViewGroup {
+public class GridLayout extends ViewGroup {
 
     // Public constants
 
@@ -909,6 +902,8 @@
             LayoutParams lp = getLayoutParams(c);
             if (firstPass) {
                 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
+                mHorizontalAxis.recordOriginalMeasurement(i);
+                mVerticalAxis.recordOriginalMeasurement(i);
             } else {
                 boolean horizontal = (mOrientation == HORIZONTAL);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -1124,6 +1119,11 @@
         public int[] locations;
         public boolean locationsValid = false;
 
+        public boolean hasWeights;
+        public boolean hasWeightsValid = false;
+        public int[] originalMeasurements;
+        public int[] deltas;
+
         boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
 
         private MutableInt parentMin = new MutableInt(0);
@@ -1200,7 +1200,10 @@
                 // we must include views that are GONE here, see introductory javadoc
                 LayoutParams lp = getLayoutParams(c);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
-                groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
+                int size = (spec.weight == 0) ?
+                        getMeasurementIncludingMargin(c, horizontal) :
+                        getOriginalMeasurements()[i] + getDeltas()[i];
+                groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
             }
         }
 
@@ -1572,8 +1575,94 @@
             return trailingMargins;
         }
 
-        private void computeLocations(int[] a) {
+        private void solve(int[] a) {
             solve(getArcs(), a);
+        }
+
+        private boolean computeHasWeights() {
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                if (spec.weight != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean hasWeights() {
+            if (!hasWeightsValid) {
+                hasWeights = computeHasWeights();
+                hasWeightsValid = true;
+            }
+            return hasWeights;
+        }
+
+        public int[] getOriginalMeasurements() {
+            if (originalMeasurements == null) {
+                originalMeasurements = new int[getChildCount()];
+            }
+            return originalMeasurements;
+        }
+
+        private void recordOriginalMeasurement(int i) {
+            if (hasWeights()) {
+                getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal);
+            }
+        }
+
+        public int[] getDeltas() {
+            if (deltas == null) {
+                deltas = new int[getChildCount()];
+            }
+            return deltas;
+        }
+
+        private void shareOutDelta() {
+            int totalDelta = 0;
+            float totalWeight = 0;
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                View c = getChildAt(i);
+                LayoutParams lp = getLayoutParams(c);
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                float weight = spec.weight;
+                if (weight != 0) {
+                    int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
+                    totalDelta += delta;
+                    totalWeight += weight;
+                }
+            }
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                float weight = spec.weight;
+                if (weight != 0) {
+                    int delta = Math.round((weight * totalDelta / totalWeight));
+                    deltas[i] = delta;
+                    // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+                    totalDelta -= delta;
+                    totalWeight -= weight;
+                }
+            }
+        }
+
+        private void solveAndDistributeSpace(int[] a) {
+            Arrays.fill(getDeltas(), 0);
+            solve(a);
+            shareOutDelta();
+            arcsValid = false;
+            forwardLinksValid = false;
+            backwardLinksValid = false;
+            groupBoundsValid = false;
+            solve(a);
+        }
+
+        private void computeLocations(int[] a) {
+            if (!hasWeights()) {
+                solve(a);
+            } else {
+                solveAndDistributeSpace(a);
+            }
             if (!orderPreserved) {
                 // Solve returns the smallest solution to the constraint system for which all
                 // values are positive. One value is therefore zero - though if the row/col
@@ -1656,6 +1745,10 @@
 
             locations = null;
 
+            originalMeasurements = null;
+            deltas = null;
+            hasWeightsValid = false;
+
             invalidateValues();
         }
 
@@ -1689,6 +1782,9 @@
      * both aspects of alignment within the cell group. It is also possible to specify a child's
      * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
      * method.
+     * <p>
+     * The weight property is also included in Spec and specifies the proportion of any
+     * excess space that is due to the associated view.
      *
      * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
      *
@@ -1730,9 +1826,11 @@
      *     <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
      *     <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
      *     <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
+     *     <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
      *     <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
      *     <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
      *     <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
+     *     <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
      * </ul>
      *
      * See {@link GridLayout} for a more complete description of the conventions
@@ -1740,8 +1838,10 @@
      *
      * @attr ref android.R.styleable#GridLayout_Layout_layout_row
      * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
      * @attr ref android.R.styleable#GridLayout_Layout_layout_column
      * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
      * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
      */
     public static class LayoutParams extends MarginLayoutParams {
@@ -1766,9 +1866,11 @@
 
         private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
         private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
+        private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
 
         private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
         private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
+        private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
 
         private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
 
@@ -1852,12 +1954,16 @@
         }
 
         /**
-         * {@inheritDoc}
+         * Copy constructor. Clones the width, height, margin values, row spec,
+         * and column spec of the source.
+         *
+         * @param source The layout params to copy from.
          */
-        public LayoutParams(LayoutParams that) {
-            super(that);
-            this.rowSpec = that.rowSpec;
-            this.columnSpec = that.columnSpec;
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            this.rowSpec = source.rowSpec;
+            this.columnSpec = source.columnSpec;
         }
 
         // AttributeSet constructors
@@ -1907,11 +2013,13 @@
 
                 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
                 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
-                this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
+                float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
+                this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
 
                 int row = a.getInt(ROW, DEFAULT_ROW);
                 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
-                this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
+                float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
+                this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
             } finally {
                 a.recycle();
             }
@@ -2146,10 +2254,9 @@
             return before - a.getAlignmentValue(c, size, ViewGroupCompat.getLayoutMode(gl));
         }
 
-        protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
+        protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
             this.flexibility &= spec.getFlexibility();
             boolean horizontal = axis.horizontal;
-            int size = gl.getMeasurementIncludingMargin(c, horizontal);
             Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
             // todo test this works correctly when the returned value is UNDEFINED
             int before = alignment.getAlignmentValue(c, size, ViewGroupCompat.getLayoutMode(gl));
@@ -2274,36 +2381,43 @@
      *   <li>{@link #spec(int, int)}</li>
      *   <li>{@link #spec(int, Alignment)}</li>
      *   <li>{@link #spec(int, int, Alignment)}</li>
+     *   <li>{@link #spec(int, float)}</li>
+     *   <li>{@link #spec(int, int, float)}</li>
+     *   <li>{@link #spec(int, Alignment, float)}</li>
+     *   <li>{@link #spec(int, int, Alignment, float)}</li>
      * </ul>
      *
      */
     public static class Spec {
         static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
+        static final float DEFAULT_WEIGHT = 0;
 
         final boolean startDefined;
         final Interval span;
         final Alignment alignment;
+        final float weight;
 
-        private Spec(boolean startDefined, Interval span, Alignment alignment) {
+        private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
             this.startDefined = startDefined;
             this.span = span;
             this.alignment = alignment;
+            this.weight = weight;
         }
 
-        private Spec(boolean startDefined, int start, int size, Alignment alignment) {
-            this(startDefined, new Interval(start, start + size), alignment);
+        private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
+            this(startDefined, new Interval(start, start + size), alignment, weight);
         }
 
         final Spec copyWriteSpan(Interval span) {
-            return new Spec(startDefined, span, alignment);
+            return new Spec(startDefined, span, alignment, weight);
         }
 
         final Spec copyWriteAlignment(Alignment alignment) {
-            return new Spec(startDefined, span, alignment);
+            return new Spec(startDefined, span, alignment, weight);
         }
 
         final int getFlexibility() {
-            return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
+            return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
         }
 
         /**
@@ -2351,6 +2465,7 @@
      * <ul>
      *     <li> {@code spec.span = [start, start + size]} </li>
      *     <li> {@code spec.alignment = alignment} </li>
+     *     <li> {@code spec.weight = weight} </li>
      * </ul>
      * <p>
      * To leave the start index undefined, use the value {@link #UNDEFINED}.
@@ -2358,9 +2473,55 @@
      * @param start     the start
      * @param size      the size
      * @param alignment the alignment
+     * @param weight    the weight
+     */
+    public static Spec spec(int start, int size, Alignment alignment, float weight) {
+        return new Spec(start != UNDEFINED, start, size, alignment, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, alignment, weight)}.
+     *
+     * @param start     the start
+     * @param alignment the alignment
+     * @param weight    the weight
+     */
+    public static Spec spec(int start, Alignment alignment, float weight) {
+        return spec(start, 1, alignment, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
+     * where {@code default_alignment} is specified in
+     * {@link android.widget.GridLayout.LayoutParams}.
+     *
+     * @param start  the start
+     * @param size   the size
+     * @param weight the weight
+     */
+    public static Spec spec(int start, int size, float weight) {
+        return spec(start, size, UNDEFINED_ALIGNMENT, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, weight)}.
+     *
+     * @param start  the start
+     * @param weight the weight
+     */
+    public static Spec spec(int start, float weight) {
+        return spec(start, 1, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, size, alignment, 0f)}.
+     *
+     * @param start     the start
+     * @param size      the size
+     * @param alignment the alignment
      */
     public static Spec spec(int start, int size, Alignment alignment) {
-        return new Spec(start != UNDEFINED, start, size, alignment);
+        return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
     }
 
     /**