diff --git a/src/api-impl/android/widget/GridLayout.java b/src/api-impl/android/widget/GridLayout.java new file mode 100644 index 00000000..3ff5dd07 --- /dev/null +++ b/src/api-impl/android/widget/GridLayout.java @@ -0,0 +1,3031 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import static android.view.Gravity.AXIS_PULL_AFTER; +import static android.view.Gravity.AXIS_PULL_BEFORE; +import static android.view.Gravity.AXIS_SPECIFIED; +import static android.view.Gravity.AXIS_X_SHIFT; +import static android.view.Gravity.AXIS_Y_SHIFT; +import static android.view.Gravity.HORIZONTAL_GRAVITY_MASK; +import static android.view.Gravity.RELATIVE_LAYOUT_DIRECTION; +import static android.view.Gravity.VERTICAL_GRAVITY_MASK; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import android.annotation.IntDef; +import android.annotation.Nullable; +// import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +// import android.graphics.Insets; +import android.graphics.Paint; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +// import android.util.LogPrinter; +import android.util.Pair; +import android.util.Printer; +import android.view.Gravity; +// import android.view.RemotableViewMethod; +import android.view.View; +import android.view.ViewGroup; +// import android.view.inspector.InspectableProperty; +// import android.widget.RemoteViews.RemoteView; +import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A layout that places its children in a rectangular grid. + *
+ * The grid is composed of a set of infinitely thin lines that separate the + * viewing area into cells. Throughout the API, grid lines are referenced + * by grid indices. A grid with {@code N} columns + * has {@code N + 1} grid indices that run from {@code 0} + * through {@code N} inclusive. Regardless of how GridLayout is + * configured, grid index {@code 0} is fixed to the leading edge of the + * container and grid index {@code N} is fixed to its trailing edge + * (after padding is taken into account). + * + *
+ * 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 + * considered to act in parallel. Such a + * group is flexible only if all of the components + * within it are flexible. Row and column groups that sit either side of a common boundary + * are instead considered to act in series. The composite group made of these two + * 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 + * 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. 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. + * + *
+ * These statements apply equally to rows as well as columns, and to groups of rows or columns. + * + *
+ * See {@link GridLayout.LayoutParams} for a full description of the + * layout parameters used by GridLayout. + * + * @attr ref android.R.styleable#GridLayout_orientation + * @attr ref android.R.styleable#GridLayout_rowCount + * @attr ref android.R.styleable#GridLayout_columnCount + * @attr ref android.R.styleable#GridLayout_useDefaultMargins + * @attr ref android.R.styleable#GridLayout_rowOrderPreserved + * @attr ref android.R.styleable#GridLayout_columnOrderPreserved + */ +//@RemoteView +public class GridLayout extends ViewGroup { + + // Public constants + + /** + * @hide + */ + @IntDef(prefix = {"HORIZONTAL", "VERTICAL"}, value = { + HORIZONTAL, + VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface Orientation {} + + /** + * The horizontal orientation. + */ + public static final int HORIZONTAL = LinearLayout.HORIZONTAL; + + /** + * The vertical orientation. + */ + public static final int VERTICAL = LinearLayout.VERTICAL; + + /** + * The constant used to indicate that a value is undefined. + * Fields can use this value to indicate that their values + * have not yet been set. Similarly, methods can return this value + * to indicate that there is no suitable value that the implementation + * can return. + * The value used for the constant (currently {@link Integer#MIN_VALUE}) is + * intended to avoid confusion between valid values whose sign may not be known. + */ + public static final int UNDEFINED = Integer.MIN_VALUE; + + /** + * @hide + */ + @IntDef(prefix = {"ALIGN_"}, value = { + ALIGN_BOUNDS, + ALIGN_MARGINS}) + @Retention(RetentionPolicy.SOURCE) + public @interface AlignmentMode {} + + /** + * This constant is an {@link #setAlignmentMode(int) alignmentMode}. + * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment + * is made between the edges of each component's raw + * view boundary: i.e. the area delimited by the component's: + * {@link android.view.View#getTop() top}, + * {@link android.view.View#getLeft() left}, + * {@link android.view.View#getBottom() bottom} and + * {@link android.view.View#getRight() right} properties. + *
+ * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode, + * children that belong to a row group that uses {@link #TOP} alignment will + * all return the same value when their {@link android.view.View#getTop()} + * method is called. + * + * @see #setAlignmentMode(int) + */ + public static final int ALIGN_BOUNDS = 0; + + /** + * This constant is an {@link #setAlignmentMode(int) alignmentMode}. + * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS}, + * the bounds of each view are extended outwards, according + * to their margins, before the edges of the resulting rectangle are aligned. + *
+ * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode, + * the quantity {@code top - layoutParams.topMargin} is the same for all children that + * belong to a row group that uses {@link #TOP} alignment. + * + * @see #setAlignmentMode(int) + */ + public static final int ALIGN_MARGINS = 1; + + // Misc constants + + static final int MAX_SIZE = 100000; + static final int DEFAULT_CONTAINER_MARGIN = 0; + static final int UNINITIALIZED_HASH = 0; + // static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName()); + static final Printer NO_PRINTER = new Printer() { + @Override + public void println(String x) { + } + }; + + // Defaults + + private static final int DEFAULT_ORIENTATION = HORIZONTAL; + private static final int DEFAULT_COUNT = UNDEFINED; + private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false; + private static final boolean DEFAULT_ORDER_PRESERVED = true; + private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; + + // TypedArray indices + + private static final int ORIENTATION = R.styleable.GridLayout_orientation; + private static final int ROW_COUNT = R.styleable.GridLayout_rowCount; + private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount; + private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins; + private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode; + private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved; + private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved; + + // Instance variables + + final Axis mHorizontalAxis = new Axis(true); + final Axis mVerticalAxis = new Axis(false); + int mOrientation = DEFAULT_ORIENTATION; + boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; + int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; + int mDefaultGap; + int mLastLayoutParamsHashCode = UNINITIALIZED_HASH; + Printer mPrinter = /*LOG_PRINTER*/ NO_PRINTER; + + // Constructors + + public GridLayout(Context context) { + this(context, null); + } + + public GridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes); + saveAttributeDataForStyleable(context, R.styleable.GridLayout, + attrs, a, defStyleAttr, defStyleRes); + try { + setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); + setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT)); + setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION)); + setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS)); + setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE)); + setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); + setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); + } finally { + a.recycle(); + } + } + + // Implementation + + /** + * Returns the current orientation. + * + * @return either {@link #HORIZONTAL} or {@link #VERTICAL} + * + * @see #setOrientation(int) + * + * @attr ref android.R.styleable#GridLayout_orientation + */ + @Orientation + /*@InspectableProperty(enumMapping = { + @InspectableProperty.EnumEntry(value = HORIZONTAL, name = "horizontal"), + @InspectableProperty.EnumEntry(value = VERTICAL, name = "vertical") + })*/ + public int getOrientation() { + return mOrientation; + } + + /** + * + * GridLayout uses the orientation property for two purposes: + *
+ * If your layout contains a {@link TextView} (or derivative: + * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is + * in multi-line mode (the default) it is normally best to leave GridLayout's + * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of + * deriving its height for a given width, but not the other way around. + *
+ * Other than the effects above, orientation does not affect the actual layout operation of + * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if + * the height of the intended layout greatly exceeds its width. + *
+ * The default value of this property is {@link #HORIZONTAL}. + * + * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL} + * + * @see #getOrientation() + * + * @attr ref android.R.styleable#GridLayout_orientation + */ + public void setOrientation(@Orientation int orientation) { + if (this.mOrientation != orientation) { + this.mOrientation = orientation; + invalidateStructure(); + requestLayout(); + } + } + + /** + * Returns the current number of rows. This is either the last value that was set + * with {@link #setRowCount(int)} or, if no such value was set, the maximum + * value of each the upper bounds defined in {@link LayoutParams#rowSpec}. + * + * @return the current number of rows + * + * @see #setRowCount(int) + * @see LayoutParams#rowSpec + * + * @attr ref android.R.styleable#GridLayout_rowCount + */ + //@InspectableProperty + public int getRowCount() { + return mVerticalAxis.getCount(); + } + + /** + * RowCount is used only to generate default row/column indices when + * they are not specified by a component's layout parameters. + * + * @param rowCount the number of rows + * + * @see #getRowCount() + * @see LayoutParams#rowSpec + * + * @attr ref android.R.styleable#GridLayout_rowCount + */ + //@RemotableViewMethod + public void setRowCount(int rowCount) { + mVerticalAxis.setCount(rowCount); + invalidateStructure(); + requestLayout(); + } + + /** + * Returns the current number of columns. This is either the last value that was set + * with {@link #setColumnCount(int)} or, if no such value was set, the maximum + * value of each the upper bounds defined in {@link LayoutParams#columnSpec}. + * + * @return the current number of columns + * + * @see #setColumnCount(int) + * @see LayoutParams#columnSpec + * + * @attr ref android.R.styleable#GridLayout_columnCount + */ + //@InspectableProperty + public int getColumnCount() { + return mHorizontalAxis.getCount(); + } + + /** + * ColumnCount is used only to generate default column/column indices when + * they are not specified by a component's layout parameters. + * + * @param columnCount the number of columns. + * + * @see #getColumnCount() + * @see LayoutParams#columnSpec + * + * @attr ref android.R.styleable#GridLayout_columnCount + */ + //@RemotableViewMethod + public void setColumnCount(int columnCount) { + mHorizontalAxis.setCount(columnCount); + invalidateStructure(); + requestLayout(); + } + + /** + * Returns whether or not this GridLayout will allocate default margins when no + * corresponding layout parameters are defined. + * + * @return {@code true} if default margins should be allocated + * + * @see #setUseDefaultMargins(boolean) + * + * @attr ref android.R.styleable#GridLayout_useDefaultMargins + */ + //@InspectableProperty + public boolean getUseDefaultMargins() { + return mUseDefaultMargins; + } + + /** + * When {@code true}, GridLayout allocates default margins around children + * based on the child's visual characteristics. Each of the + * margins so defined may be independently overridden by an assignment + * to the appropriate layout parameter. + *
+ * When {@code false}, the default value of all margins is zero. + *
+ * When setting to {@code true}, consider setting the value of the + * {@link #setAlignmentMode(int) alignmentMode} + * property to {@link #ALIGN_BOUNDS}. + *
+ * The default value of this property is {@code false}. + * + * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins + * + * @see #getUseDefaultMargins() + * @see #setAlignmentMode(int) + * + * @see ViewGroup.MarginLayoutParams#leftMargin + * @see ViewGroup.MarginLayoutParams#topMargin + * @see ViewGroup.MarginLayoutParams#rightMargin + * @see ViewGroup.MarginLayoutParams#bottomMargin + * + * @attr ref android.R.styleable#GridLayout_useDefaultMargins + */ + public void setUseDefaultMargins(boolean useDefaultMargins) { + this.mUseDefaultMargins = useDefaultMargins; + requestLayout(); + } + + /** + * Returns the alignment mode. + * + * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} + * + * @see #ALIGN_BOUNDS + * @see #ALIGN_MARGINS + * + * @see #setAlignmentMode(int) + * + * @attr ref android.R.styleable#GridLayout_alignmentMode + */ + @AlignmentMode + /*@InspectableProperty(enumMapping = { + @InspectableProperty.EnumEntry(value = ALIGN_BOUNDS, name = "alignBounds") + , + @InspectableProperty.EnumEntry(value = ALIGN_MARGINS, name = "alignMargins"), + })*/ + public int getAlignmentMode() { + return mAlignmentMode; + } + + /** + * Sets the alignment mode to be used for all of the alignments between the + * children of this container. + *
+ * The default value of this property is {@link #ALIGN_MARGINS}. + * + * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} + * + * @see #ALIGN_BOUNDS + * @see #ALIGN_MARGINS + * + * @see #getAlignmentMode() + * + * @attr ref android.R.styleable#GridLayout_alignmentMode + */ + //@RemotableViewMethod + public void setAlignmentMode(@AlignmentMode int alignmentMode) { + this.mAlignmentMode = alignmentMode; + requestLayout(); + } + + /** + * Returns whether or not row boundaries are ordered by their grid indices. + * + * @return {@code true} if row boundaries must appear in the order of their indices, + * {@code false} otherwise + * + * @see #setRowOrderPreserved(boolean) + * + * @attr ref android.R.styleable#GridLayout_rowOrderPreserved + */ + //@InspectableProperty + public boolean isRowOrderPreserved() { + return mVerticalAxis.isOrderPreserved(); + } + + /** + * When this property is {@code true}, GridLayout is forced to place the row boundaries + * so that their associated grid indices are in ascending order in the view. + *
+ * When this property is {@code false} GridLayout is at liberty to place the vertical row + * boundaries in whatever order best fits the given constraints. + *
+ * The default value of this property is {@code true}. + + * @param rowOrderPreserved {@code true} to force GridLayout to respect the order + * of row boundaries + * + * @see #isRowOrderPreserved() + * + * @attr ref android.R.styleable#GridLayout_rowOrderPreserved + */ + public void setRowOrderPreserved(boolean rowOrderPreserved) { + mVerticalAxis.setOrderPreserved(rowOrderPreserved); + invalidateStructure(); + requestLayout(); + } + + /** + * Returns whether or not column boundaries are ordered by their grid indices. + * + * @return {@code true} if column boundaries must appear in the order of their indices, + * {@code false} otherwise + * + * @see #setColumnOrderPreserved(boolean) + * + * @attr ref android.R.styleable#GridLayout_columnOrderPreserved + */ + //@InspectableProperty + public boolean isColumnOrderPreserved() { + return mHorizontalAxis.isOrderPreserved(); + } + + /** + * When this property is {@code true}, GridLayout is forced to place the column boundaries + * so that their associated grid indices are in ascending order in the view. + *
+ * When this property is {@code false} GridLayout is at liberty to place the horizontal column + * boundaries in whatever order best fits the given constraints. + *
+ * The default value of this property is {@code true}.
+ *
+ * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
+ * of column boundaries.
+ *
+ * @see #isColumnOrderPreserved()
+ *
+ * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ */
+ public void setColumnOrderPreserved(boolean columnOrderPreserved) {
+ mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
+ invalidateStructure();
+ requestLayout();
+ }
+
+ /**
+ * Return the printer that will log diagnostics from this layout.
+ *
+ * @see #setPrinter(android.util.Printer)
+ *
+ * @return the printer associated with this view
+ *
+ * @hide
+ */
+ public Printer getPrinter() {
+ return mPrinter;
+ }
+
+ /**
+ * Set the printer that will log diagnostics from this layout.
+ * The default value is created by {@link android.util.LogPrinter}.
+ *
+ * @param printer the printer associated with this layout
+ *
+ * @see #getPrinter()
+ *
+ * @hide
+ */
+ public void setPrinter(Printer printer) {
+ this.mPrinter = (printer == null) ? NO_PRINTER : printer;
+ }
+
+ // Static utility methods
+
+ static int max2(int[] a, int valueIfEmpty) {
+ int result = valueIfEmpty;
+ for (int i = 0, N = a.length; i < N; i++) {
+ result = Math.max(result, a[i]);
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ static
+ * GridLayout supports both row and column spanning and arbitrary forms of alignment within
+ * each cell group. The fundamental parameters associated with each cell group are
+ * gathered into their vertical and horizontal components and stored
+ * in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
+ * {@link GridLayout.Spec Specs} are immutable structures
+ * and may be shared between the layout parameters of different children.
+ *
+ * The row and column specs contain the leading and trailing indices along each axis
+ * and together specify the four grid indices that delimit the cells of this cell group.
+ *
+ * The alignment properties of the row and column specs together specify
+ * 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 associated view.
+ *
+ *
+ * Intervals are immutable so may be passed as values and used as keys in hash tables.
+ * It is not necessary to have multiple instances of Intervals which have the same
+ * {@link #min} and {@link #max} values.
+ *
+ * Intervals are often written as {@code [min, max]} and represent the set of values
+ * {@code x} such that {@code min <= x < max}.
+ */
+ final static class Interval {
+ /**
+ * The minimum value.
+ */
+ public final int min;
+
+ /**
+ * The maximum value.
+ */
+ public final int max;
+
+ /**
+ * Construct a new Interval, {@code interval}, where:
+ *
+ * The grid indices are the leading and trailing edges of this cell group.
+ * See {@link GridLayout} for a description of the conventions used by GridLayout
+ * for grid indices.
+ *
+ * The alignment property specifies how cells should be aligned in this group.
+ * For row groups, this specifies the vertical alignment.
+ * For column groups, this specifies the horizontal alignment.
+ *
+ * Use the following static methods to create specs:
+ *
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
+ *
+ * @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 spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
+ }
+
+ /**
+ * Return a Spec, {@code spec}, where:
+ *
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
+ *
+ * @param start the start index
+ * @param alignment the alignment
+ *
+ * @see #spec(int, int, Alignment)
+ */
+ public static Spec spec(int start, Alignment alignment) {
+ return spec(start, 1, alignment);
+ }
+
+ /**
+ * Return a Spec, {@code spec}, where:
+ *
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
+ *
+ * @param start the start
+ * @param size the size
+ *
+ * @see #spec(int, Alignment)
+ */
+ public static Spec spec(int start, int size) {
+ return spec(start, size, UNDEFINED_ALIGNMENT);
+ }
+
+ /**
+ * Return a Spec, {@code spec}, where:
+ *
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
+ *
+ * @param start the start index
+ *
+ * @see #spec(int, int)
+ */
+ public static Spec spec(int start) {
+ return spec(start, 1);
+ }
+
+ /**
+ * Alignments specify where a view should be placed within a cell group and
+ * what size it should be.
+ *
+ * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
+ * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
+ * {@code alignment}. Overall placement of the view in the cell
+ * group is specified by the two alignments which act along each axis independently.
+ *
+ * The GridLayout class defines the most common alignments used in general layout:
+ * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
+ * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
+ */
+ /*
+ * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
+ * to return the appropriate value for the type of alignment being defined.
+ * The enclosing algorithms position the children
+ * so that the locations defined by the alignment values
+ * are the same for all of the views in a group.
+ *
+ */
+ public static abstract class Alignment {
+ Alignment() {
+ }
+
+ abstract int getGravityOffset(View view, int cellDelta);
+
+ /**
+ * Returns an alignment value. In the case of vertical alignments the value
+ * returned should indicate the distance from the top of the view to the
+ * alignment location.
+ * For horizontal alignments measurement is made from the left edge of the component.
+ *
+ * @param view the view to which this alignment should be applied
+ * @param viewSize the measured size of the view
+ * @param mode the basis of alignment: CLIP or OPTICAL
+ * @return the alignment value
+ */
+ abstract int getAlignmentValue(View view, int viewSize, int mode);
+
+ /**
+ * Returns the size of the view specified by this alignment.
+ * In the case of vertical alignments this method should return a height; for
+ * horizontal alignments this method should return the width.
+ *
+ * The default implementation returns {@code viewSize}.
+ *
+ * @param view the view to which this alignment should be applied
+ * @param viewSize the measured size of the view
+ * @param cellSize the size of the cell into which this view will be placed
+ * @return the aligned size
+ */
+ int getSizeInCell(View view, int viewSize, int cellSize) {
+ return viewSize;
+ }
+
+ Bounds getBounds() {
+ return new Bounds();
+ }
+ }
+
+ //@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return UNDEFINED;
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ return UNDEFINED;
+ }
+ };
+
+ /**
+ * Indicates that a view should be aligned with the start
+ * edges of the other views in its cell group.
+ */
+ private static final Alignment LEADING = new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return 0;
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ return 0;
+ }
+ };
+
+ /**
+ * Indicates that a view should be aligned with the end
+ * edges of the other views in its cell group.
+ */
+ private static final Alignment TRAILING = new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return cellDelta;
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ return viewSize;
+ }
+ };
+
+ /**
+ * Indicates that a view should be aligned with the top
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment TOP = LEADING;
+
+ /**
+ * Indicates that a view should be aligned with the bottom
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment BOTTOM = TRAILING;
+
+ /**
+ * Indicates that a view should be aligned with the start
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment START = LEADING;
+
+ /**
+ * Indicates that a view should be aligned with the end
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment END = TRAILING;
+
+ private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
+ return new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return (!/*view.isLayoutRtl()*/false ? ltr : rtl).getGravityOffset(view, cellDelta);
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ return (!/*view.isLayoutRtl()*/false ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
+ }
+ };
+ }
+
+ /**
+ * Indicates that a view should be aligned with the left
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment LEFT = createSwitchingAlignment(START, END);
+
+ /**
+ * Indicates that a view should be aligned with the right
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment RIGHT = createSwitchingAlignment(END, START);
+
+ /**
+ * Indicates that a view should be centered with the other views in its cell group.
+ * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
+ * LayoutParams#columnSpec columnSpecs}.
+ */
+ public static final Alignment CENTER = new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return cellDelta >> 1;
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ return viewSize >> 1;
+ }
+ };
+
+ /**
+ * Indicates that a view should be aligned with the baselines
+ * of the other views in its cell group.
+ * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
+ *
+ * @see View#getBaseline()
+ */
+ public static final Alignment BASELINE = new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return 0; // baseline gravity is top
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ if (view.getVisibility() == GONE) {
+ return 0;
+ }
+ int baseline = view.getBaseline();
+ return baseline == -1 ? UNDEFINED : baseline;
+ }
+
+ @Override
+ public Bounds getBounds() {
+ return new Bounds() {
+ /*
+ In a baseline aligned row in which some components define a baseline
+ and some don't, we need a third variable to properly account for all
+ the sizes. This tracks the maximum size of all the components -
+ including those that don't define a baseline.
+ */
+ private int size;
+
+ @Override
+ protected void reset() {
+ super.reset();
+ size = Integer.MIN_VALUE;
+ }
+
+ @Override
+ protected void include(int before, int after) {
+ super.include(before, after);
+ size = max(size, before + after);
+ }
+
+ @Override
+ protected int size(boolean min) {
+ return max(super.size(min), size);
+ }
+
+ @Override
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
+ return max(0, super.getOffset(gl, c, a, size, hrz));
+ }
+ };
+ }
+ };
+
+ /**
+ * Indicates that a view should expanded to fit the boundaries of its cell group.
+ * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
+ * {@link LayoutParams#columnSpec columnSpecs}.
+ */
+ public static final Alignment FILL = new Alignment() {
+ @Override
+ int getGravityOffset(View view, int cellDelta) {
+ return 0;
+ }
+
+ @Override
+ public int getAlignmentValue(View view, int viewSize, int mode) {
+ return UNDEFINED;
+ }
+
+ @Override
+ public int getSizeInCell(View view, int viewSize, int cellSize) {
+ return cellSize;
+ }
+ };
+
+ static boolean canStretch(int flexibility) {
+ return (flexibility & CAN_STRETCH) != 0;
+ }
+
+ private static final int INFLEXIBLE = 0;
+ private static final int CAN_STRETCH = 2;
+}
diff --git a/src/api-impl/meson.build b/src/api-impl/meson.build
index 1b9331f1..ed68cdfb 100644
--- a/src/api-impl/meson.build
+++ b/src/api-impl/meson.build
@@ -599,6 +599,7 @@ srcs = [
'android/widget/FilterQueryProvider.java',
'android/widget/FrameLayout.java',
'android/widget/Gallery.java',
+ 'android/widget/GridLayout.java',
'android/widget/GridView.java',
'android/widget/HeaderViewListAdapter.java',
'android/widget/HeterogeneousExpandableList.java',
WRAP_CONTENT and MATCH_PARENT
+ *
+ * Because the default values of the {@link #width} and {@link #height}
+ * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
+ * declared in the layout parameters of GridLayout's children. In addition,
+ * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
+ * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
+ * instead controlled by the principle of flexibility,
+ * as discussed in {@link GridLayout}.
+ *
+ * Summary
+ *
+ * You should not need to use either of the special size values:
+ * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
+ * a GridLayout.
+ *
+ * Default values
+ *
+ *
+ *
+ *
+ * See {@link GridLayout} for a more complete description of the conventions
+ * used by GridLayout in the interpretation of the properties of this class.
+ *
+ * @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 {
+
+ // Default values
+
+ private static final int DEFAULT_WIDTH = WRAP_CONTENT;
+ private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
+ private static final int DEFAULT_MARGIN = UNDEFINED;
+ private static final int DEFAULT_ROW = UNDEFINED;
+ private static final int DEFAULT_COLUMN = UNDEFINED;
+ private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
+ private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
+
+ // TypedArray indices
+
+ private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin;
+ private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft;
+ private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop;
+ private static final int RIGHT_MARGIN =
+ R.styleable.ViewGroup_MarginLayout_layout_marginRight;
+ private static final int BOTTOM_MARGIN =
+ R.styleable.ViewGroup_MarginLayout_layout_marginBottom;
+ 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;
+
+ // Instance variables
+
+ /**
+ * 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;
+
+ // Constructors
+
+ private LayoutParams(
+ int width, int height,
+ int left, int top, int right, int bottom,
+ Spec rowSpec, Spec columnSpec) {
+ super(width, height);
+ setMargins(left, top, right, bottom);
+ this.rowSpec = rowSpec;
+ this.columnSpec = columnSpec;
+ }
+
+ /**
+ * Constructs a new LayoutParams instance for this .row = {@link #UNDEFINED} .rowSpan = 1 .alignment = {@link #BASELINE} .weight = 0 .column = {@link #UNDEFINED} .columnSpan = 1 .alignment = {@link #START} .weight = 0 rowSpec
+ * and columnSpec. All other fields are initialized with
+ * default values as defined in {@link LayoutParams}.
+ *
+ * @param rowSpec the rowSpec
+ * @param columnSpec the columnSpec
+ */
+ public LayoutParams(Spec rowSpec, Spec columnSpec) {
+ this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
+ rowSpec, columnSpec);
+ }
+
+ /**
+ * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
+ */
+ public LayoutParams() {
+ this(Spec.UNDEFINED, Spec.UNDEFINED);
+ }
+
+ // Copying constructors
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(ViewGroup.LayoutParams params) {
+ super(params);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(MarginLayoutParams params) {
+ super(params);
+ }
+
+ /**
+ * 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 source) {
+ super(source);
+
+ this.rowSpec = source.rowSpec;
+ this.columnSpec = source.columnSpec;
+ }
+
+ // AttributeSet constructors
+
+ /**
+ * {@inheritDoc}
+ *
+ * Values not defined in the attribute set take the default values
+ * defined in {@link LayoutParams}.
+ */
+ public LayoutParams(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ reInitSuper(context, attrs);
+ init(context, attrs);
+ }
+
+ // Implementation
+
+ // Reinitialise the margins using a different default policy than MarginLayoutParams.
+ // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
+ // so that a layout manager default can be accessed post set up. We need this as, at the
+ // point of installation, we do not know how many rows/cols there are and therefore
+ // which elements are positioned next to the container's trailing edges. We need to
+ // know this as margins around the container's boundary should have different
+ // defaults to those between peers.
+
+ // This method could be parametrized and moved into MarginLayout.
+ private void reInitSuper(Context context, AttributeSet attrs) {
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
+ try {
+ int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
+
+ this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
+ this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
+ this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
+ this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
+ try {
+ int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
+
+ int column = a.getInt(COLUMN, DEFAULT_COLUMN);
+ int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
+ 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);
+ float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
+ this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ /**
+ * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
+ * See {@link Gravity}.
+ *
+ * @param gravity the new gravity value
+ *
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+ */
+ public void setGravity(int gravity) {
+ rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
+ columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
+ }
+
+ @Override
+ protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
+ this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
+ this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
+ }
+
+ final void setRowSpecSpan(Interval span) {
+ rowSpec = rowSpec.copyWriteSpan(span);
+ }
+
+ final void setColumnSpecSpan(Interval span) {
+ columnSpec = columnSpec.copyWriteSpan(span);
+ }
+
+ @Override
+ public boolean equals(@Nullable 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;
+ }
+ }
+
+ /*
+ In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
+ Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
+ */
+ final static class Arc {
+ public final Interval span;
+ public final MutableInt value;
+ public boolean valid = true;
+
+ public Arc(Interval span, MutableInt value) {
+ this.span = span;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return span + " " + (!valid ? "+>" : "->") + " " + value;
+ }
+ }
+
+ // A mutable Integer - used to avoid heap allocation during the layout operation
+
+ final static class MutableInt {
+ public int value;
+
+ public MutableInt() {
+ reset();
+ }
+
+ public MutableInt(int value) {
+ this.value = value;
+ }
+
+ public void reset() {
+ value = Integer.MIN_VALUE;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(value);
+ }
+ }
+
+ final static class Assoc
+ *
+ *
+ * @param min the minimum value.
+ * @param max the maximum value.
+ */
+ public Interval(int min, int max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ int size() {
+ return max - min;
+ }
+
+ Interval inverse() {
+ return new Interval(max, min);
+ }
+
+ /**
+ * Returns {@code true} if the {@link #getClass class},
+ * {@link #min} and {@link #max} properties of this Interval and the
+ * supplied parameter are pairwise equal; {@code false} otherwise.
+ *
+ * @param that the object to compare this interval with
+ *
+ * @return {@code true} if the specified object is equal to this
+ * {@code Interval}, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(@Nullable Object that) {
+ if (this == that) {
+ return true;
+ }
+ if (that == null || getClass() != that.getClass()) {
+ return false;
+ }
+
+ Interval interval = (Interval)that;
+
+ if (max != interval.max) {
+ return false;
+ }
+ // noinspection RedundantIfStatement
+ if (min != interval.min) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = min;
+ result = 31 * result + max;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + min + ", " + max + "]";
+ }
+ }
+
+ /**
+ * A Spec defines the horizontal or vertical characteristics of a group of
+ * cells. Each spec. defines the grid indices and alignment
+ * along the appropriate axis.
+ *
+ *
+ *
+ */
+ 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, float weight) {
+ this.startDefined = startDefined;
+ this.span = span;
+ this.alignment = alignment;
+ this.weight = weight;
+ }
+
+ private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
+ this(startDefined, new Interval(start, start + size), alignment, weight);
+ }
+
+ private Alignment getAbsoluteAlignment(boolean horizontal) {
+ if (alignment != UNDEFINED_ALIGNMENT) {
+ return alignment;
+ }
+ if (weight == 0f) {
+ return horizontal ? START : BASELINE;
+ }
+ return FILL;
+ }
+
+ final Spec copyWriteSpan(Interval span) {
+ return new Spec(startDefined, span, alignment, weight);
+ }
+
+ final Spec copyWriteAlignment(Alignment alignment) {
+ return new Spec(startDefined, span, alignment, weight);
+ }
+
+ final int getFlexibility() {
+ return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
+ }
+
+ /**
+ * Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
+ * properties of this Spec and the supplied parameter are pairwise equal,
+ * {@code false} otherwise.
+ *
+ * @param that the object to compare this spec with
+ *
+ * @return {@code true} if the specified object is equal to this
+ * {@code Spec}; {@code false} otherwise
+ */
+ @Override
+ public boolean equals(@Nullable Object that) {
+ if (this == that) {
+ return true;
+ }
+ if (that == null || getClass() != that.getClass()) {
+ return false;
+ }
+
+ Spec spec = (Spec)that;
+
+ if (!alignment.equals(spec.alignment)) {
+ return false;
+ }
+ // noinspection RedundantIfStatement
+ if (!span.equals(spec.span)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = span.hashCode();
+ result = 31 * result + alignment.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * Return a Spec, {@code spec}, where:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *