diff --git a/api/current.txt b/api/current.txt index 807cfd6ad7806..9de395b643df9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -749,6 +749,7 @@ package android { field public static final int layout_centerVertical = 16843153; // 0x1010191 field public static final int layout_column = 16843084; // 0x101014c field public static final int layout_columnSpan = 16843645; // 0x101037d + field public static final int layout_columnWeight = 16843868; // 0x101045c field public static final int layout_gravity = 16842931; // 0x10100b3 field public static final int layout_height = 16842997; // 0x10100f5 field public static final int layout_margin = 16842998; // 0x10100f6 @@ -760,6 +761,7 @@ package android { field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c + field public static final int layout_rowWeight = 16843867; // 0x101045b field public static final int layout_scale = 16843155; // 0x1010193 field public static final int layout_span = 16843085; // 0x101014d field public static final int layout_toEndOf = 16843704; // 0x10103b8 @@ -36576,6 +36578,10 @@ package android.widget { method public void setRowCount(int); method public void setRowOrderPreserved(boolean); method public void setUseDefaultMargins(boolean); + method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment, float); + method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment, float); + method public static android.widget.GridLayout.Spec spec(int, int, float); + method public static android.widget.GridLayout.Spec spec(int, float); method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment); method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment); method public static android.widget.GridLayout.Spec spec(int, int); diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 851160163e59a..97f89aaf82233 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -104,14 +104,16 @@ import static java.lang.Math.min; * *

Excess Space Distribution

* - * GridLayout's distribution of excess space is based on priority - * rather than weight. + * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight. + * In the event that no weights are specified, the previous conventions are respected and + * columns and rows are taken as flexible if their views specify some form of alignment + * within their groups. *

- * 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 flexible 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 flexible in that direction. If no weight or alignment was set, * the component is instead assumed to be inflexible. *

* Multiple components in the same row or column group are @@ -122,12 +124,16 @@ import static java.lang.Math.min; * elements is flexible if one of its elements is flexible. *

* 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. *

* When the principle of flexibility does not provide complete disambiguation, * GridLayout's algorithms favour rows and columns that are closer to its right - * and bottom edges. + * and bottom 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. * *

Interpretation of GONE

* @@ -140,18 +146,6 @@ import static java.lang.Math.min; * had never been added to it. * These statements apply equally to rows as well as columns, and to groups of rows or columns. * - *
Limitations
- * - * GridLayout does not provide support for the principle of weight, 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. - *

- * 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. *

* See {@link GridLayout.LayoutParams} for a full description of the * layout parameters used by GridLayout. @@ -1693,8 +1687,74 @@ public class GridLayout extends ViewGroup { return trailingMargins; } - private void computeLocations(int[] a) { + private void solve(int[] a) { solve(getArcs(), a); + } + + private void resetDeltas() { + Bounds[] values = getGroupBounds().values; + for (int i = 0, N = values.length; i < N; i++) { + values[i].shareOfDelta = 0; + } + } + + private boolean requiresWeightDistribution() { + Bounds[] values = getGroupBounds().values; + for (int i = 0, N = values.length; i < N; i++) { + if (values[i].weight != 0) { + return true; + } + } + return false; + } + + private void shareOutDelta(int[] locations) { + PackedMap groupBounds = getGroupBounds(); + Spec[] specs = groupBounds.keys; + Bounds[] bounds = groupBounds.values; + int totalDelta = 0; + float totalWeight = 0; + final int N = bounds.length; + + for (int i = 0; i < N; i++) { + Spec spec = specs[i]; + Bounds bound = bounds[i]; + if (bound.weight != 0) { + int minSize = bound.size(true); + Interval span = spec.span; + int actualSize = locations[span.max] - locations[span.min]; + int delta = actualSize - minSize; + totalDelta += delta; + totalWeight += bound.weight; + } + } + for (int i = 0; i < N; i++) { + Bounds bound = bounds[i]; + float weight = bound.weight; + if (weight != 0) { + int delta = Math.round((weight * totalDelta / totalWeight)); + bound.shareOfDelta = delta; + // the two adjustments below compensate for the rounding above + totalDelta -= delta; + totalWeight -= weight; + } + } + } + + private void solveAndDistributeSpace(int[] a) { + resetDeltas(); + solve(a); + if (requiresWeightDistribution()) { + shareOutDelta(a); + arcsValid = false; + forwardLinksValid = false; + backwardLinksValid = false; + solve(a); + } + } + + private void computeLocations(int[] a) { + 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 @@ -1810,6 +1870,23 @@ public class GridLayout extends ViewGroup { * 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. + *

+ * The weight property is also included in Spec and specifies the proportion of any + * excess space that is due to the enclosing row or column. + * GridLayout's model of flexibility and weight is broadly based on the physical properties of + * systems of mechanical springs. For example, a column in a GridLayout is modeled as + * if a set of springs were attached in parallel at their ends. Columns are therefore + * flexible only if and only if all views inside them are flexible. Similarly, the combined + * weight of a column is defined as the minimum of the weights of the components it + * contains. So, if any one component in a column of components has a weight of zero, + * the entire group has a weight of zero and will not receive any of the + * excess space that GridLayout distributes in its second + * (internal) layout pass. The default weight of a component is zero, + * reflecting inflexibility, but the default weight of a column (with nothing in it) + * is effectively infinite, reflecting the fact that a parallel group of + * springs is infinitely flexible when it contains no springs. + *

+ * The above comments apply equally to rows and groups of rows and columns. * *

WRAP_CONTENT and MATCH_PARENT

* @@ -1851,9 +1928,11 @@ public class GridLayout extends ViewGroup { *
  • {@link #rowSpec}.row = {@link #UNDEFINED}
  • *
  • {@link #rowSpec}.rowSpan = 1
  • *
  • {@link #rowSpec}.alignment = {@link #BASELINE}
  • + *
  • {@link #rowSpec}.weight = 0
  • *
  • {@link #columnSpec}.column = {@link #UNDEFINED}
  • *
  • {@link #columnSpec}.columnSpan = 1
  • *
  • {@link #columnSpec}.alignment = {@link #START}
  • + *
  • {@link #columnSpec}.weight = 0
  • * * * See {@link GridLayout} for a more complete description of the conventions @@ -1861,8 +1940,10 @@ public class GridLayout extends ViewGroup { * * @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 { @@ -1889,9 +1970,11 @@ public class GridLayout extends ViewGroup { 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; @@ -2034,11 +2117,13 @@ public class GridLayout extends ViewGroup { 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(); } @@ -2244,6 +2329,8 @@ public class GridLayout extends ViewGroup { public int before; public int after; public int flexibility; // we're flexible iff all included specs are flexible + public float weight; // the min of the weights of the individual specs + public int shareOfDelta; private Bounds() { reset(); @@ -2253,6 +2340,7 @@ public class GridLayout extends ViewGroup { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; flexibility = CAN_STRETCH; // from the above, we're flexible when empty + weight = Float.MAX_VALUE; // default is large => row/cols take all slack when empty } protected void include(int before, int after) { @@ -2266,7 +2354,7 @@ public class GridLayout extends ViewGroup { return MAX_SIZE; } } - return before + after; + return before + after + shareOfDelta; } protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { @@ -2275,6 +2363,7 @@ public class GridLayout extends ViewGroup { protected final void include(GridLayout gl, View c, Spec spec, Axis axis) { this.flexibility &= spec.getFlexibility(); + weight = Math.min(weight, spec.weight); boolean horizontal = axis.horizontal; int size = gl.getMeasurementIncludingMargin(c, horizontal); Alignment alignment = gl.getAlignment(spec.alignment, horizontal); @@ -2401,36 +2490,43 @@ public class GridLayout extends ViewGroup { *
  • {@link #spec(int, int)}
  • *
  • {@link #spec(int, Alignment)}
  • *
  • {@link #spec(int, int, Alignment)}
  • + *
  • {@link #spec(int, float)}
  • + *
  • {@link #spec(int, int, float)}
  • + *
  • {@link #spec(int, Alignment, float)}
  • + *
  • {@link #spec(int, int, Alignment, float)}
  • * * */ 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; } /** @@ -2478,6 +2574,7 @@ public class GridLayout extends ViewGroup { * *

    * To leave the start index undefined, use the value {@link #UNDEFINED}. @@ -2485,9 +2582,55 @@ public class GridLayout extends ViewGroup { * @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); } /** diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c05dfcab5b869..9eb6f7426595d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4057,6 +4057,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> + + @@ -4065,6 +4068,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 7dc967cb48637..42ed318df5442 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2178,6 +2178,8 @@ + +