Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.gridlayout.widget;
     18 
     19 import static android.view.Gravity.AXIS_PULL_AFTER;
     20 import static android.view.Gravity.AXIS_PULL_BEFORE;
     21 import static android.view.Gravity.AXIS_SPECIFIED;
     22 import static android.view.Gravity.AXIS_X_SHIFT;
     23 import static android.view.Gravity.AXIS_Y_SHIFT;
     24 import static android.view.Gravity.HORIZONTAL_GRAVITY_MASK;
     25 import static android.view.Gravity.VERTICAL_GRAVITY_MASK;
     26 import static android.view.View.MeasureSpec.EXACTLY;
     27 import static android.view.View.MeasureSpec.makeMeasureSpec;
     28 
     29 import static java.lang.Math.max;
     30 import static java.lang.Math.min;
     31 
     32 import android.content.Context;
     33 import android.content.res.TypedArray;
     34 import android.graphics.Canvas;
     35 import android.graphics.Paint;
     36 import android.util.AttributeSet;
     37 import android.util.Log;
     38 import android.util.LogPrinter;
     39 import android.util.Pair;
     40 import android.util.Printer;
     41 import android.view.Gravity;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import android.widget.LinearLayout;
     45 
     46 import androidx.core.view.GravityCompat;
     47 import androidx.core.view.ViewCompat;
     48 import androidx.core.view.ViewGroupCompat;
     49 import androidx.gridlayout.R;
     50 import androidx.legacy.widget.Space;
     51 
     52 import java.lang.reflect.Array;
     53 import java.util.ArrayList;
     54 import java.util.Arrays;
     55 import java.util.HashMap;
     56 import java.util.List;
     57 import java.util.Map;
     58 
     59 /**
     60  * A layout that places its children in a rectangular <em>grid</em>.
     61  * <p>
     62  * The grid is composed of a set of infinitely thin lines that separate the
     63  * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
     64  * by grid <em>indices</em>. A grid with {@code N} columns
     65  * has {@code N + 1} grid indices that run from {@code 0}
     66  * through {@code N} inclusive. Regardless of how GridLayout is
     67  * configured, grid index {@code 0} is fixed to the leading edge of the
     68  * container and grid index {@code N} is fixed to its trailing edge
     69  * (after padding is taken into account).
     70  *
     71  * <h4>Row and Column Specs</h4>
     72  *
     73  * Children occupy one or more contiguous cells, as defined
     74  * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and
     75  * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters.
     76  * Each spec defines the set of rows or columns that are to be
     77  * occupied; and how children should be aligned within the resulting group of cells.
     78  * Although cells do not normally overlap in a GridLayout, GridLayout does
     79  * not prevent children being defined to occupy the same cell or group of cells.
     80  * In this case however, there is no guarantee that children will not themselves
     81  * overlap after the layout operation completes.
     82  *
     83  * <h4>Default Cell Assignment</h4>
     84  *
     85  * If a child does not specify the row and column indices of the cell it
     86  * wishes to occupy, GridLayout assigns cell locations automatically using its:
     87  * {@link GridLayout#setOrientation(int) orientation},
     88  * {@link GridLayout#setRowCount(int) rowCount} and
     89  * {@link GridLayout#setColumnCount(int) columnCount} properties.
     90  *
     91  * <h4>Space</h4>
     92  *
     93  * Space between children may be specified either by using instances of the
     94  * dedicated {@link android.widget.Space} view or by setting the
     95  *
     96  * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
     97  * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
     98  * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
     99  * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
    100  *
    101  * layout parameters. When the
    102  * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
    103  * property is set, default margins around children are automatically
    104  * allocated based on the prevailing UI style guide for the platform.
    105  * Each of the margins so defined may be independently overridden by an assignment
    106  * to the appropriate layout parameter.
    107  * Default values will generally produce a reasonable spacing between components
    108  * but values may change between different releases of the platform.
    109  *
    110  * <h4>Excess Space Distribution</h4>
    111  *
    112  * GridLayout's distribution of excess space accommodates the principle of weight.
    113  * In the event that no weights are specified, columns and rows are taken as
    114  * flexible if their views specify some form of alignment within their groups.
    115  * <p>
    116  * The flexibility of a view is therefore influenced by its alignment which is,
    117  * in turn, typically defined by setting the
    118  * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
    119  * If either a weight or alignment were defined along a given axis then the component
    120  * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
    121  * the component is instead assumed to be <em>inflexible</em>.
    122  * <p>
    123  * Multiple components in the same row or column group are
    124  * considered to act in <em>parallel</em>. Such a
    125  * group is flexible only if <em>all</em> of the components
    126  * within it are flexible. Row and column groups that sit either side of a common boundary
    127  * are instead considered to act in <em>series</em>. The composite group made of these two
    128  * elements is flexible if <em>one</em> of its elements is flexible.
    129  * <p>
    130  * To make a column stretch, make sure all of the components inside it define a
    131  * weight or a gravity. To prevent a column from stretching, ensure that one of the components
    132  * in the column does not define a weight or a gravity.
    133  * <p>
    134  * When the principle of flexibility does not provide complete disambiguation,
    135  * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
    136  * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
    137  * parameters as a constraint in the a set of variables that define the grid-lines along a
    138  * given axis. During layout, GridLayout solves the constraints so as to return the unique
    139  * solution to those constraints for which all variables are less-than-or-equal-to
    140  * the corresponding value in any other valid solution.
    141  *
    142  * <h4>Interpretation of GONE</h4>
    143  *
    144  * For layout purposes, GridLayout treats views whose visibility status is
    145  * {@link View#GONE GONE}, as having zero width and height. This is subtly different from
    146  * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked
    147  * view was alone in a column, that column would itself collapse to zero width if and only if
    148  * no gravity was defined on the view. If gravity was defined, then the gone-marked
    149  * view has no effect on the layout and the container should be laid out as if the view
    150  * had never been added to it. GONE views are taken to have zero weight during excess space
    151  * distribution.
    152  * <p>
    153  * These statements apply equally to rows as well as columns, and to groups of rows or columns.
    154  *
    155  *
    156  * <p>
    157  * See {@link GridLayout.LayoutParams} for a full description of the
    158  * layout parameters used by GridLayout.
    159  *
    160  * @attr name android:orientation
    161  * @attr name android:rowCount
    162  * @attr name android:columnCount
    163  * @attr name android:useDefaultMargins
    164  * @attr name android:rowOrderPreserved
    165  * @attr name android:columnOrderPreserved
    166  */
    167 public class GridLayout extends ViewGroup {
    168 
    169     // Public constants
    170 
    171     /**
    172      * The horizontal orientation.
    173      */
    174     public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
    175 
    176     /**
    177      * The vertical orientation.
    178      */
    179     public static final int VERTICAL = LinearLayout.VERTICAL;
    180 
    181     /**
    182      * The constant used to indicate that a value is undefined.
    183      * Fields can use this value to indicate that their values
    184      * have not yet been set. Similarly, methods can return this value
    185      * to indicate that there is no suitable value that the implementation
    186      * can return.
    187      * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
    188      * intended to avoid confusion between valid values whose sign may not be known.
    189      */
    190     public static final int UNDEFINED = Integer.MIN_VALUE;
    191 
    192     /**
    193      * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
    194      * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
    195      * is made between the edges of each component's raw
    196      * view boundary: i.e. the area delimited by the component's:
    197      * {@link android.view.View#getTop() top},
    198      * {@link android.view.View#getLeft() left},
    199      * {@link android.view.View#getBottom() bottom} and
    200      * {@link android.view.View#getRight() right} properties.
    201      * <p>
    202      * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode,
    203      * children that belong to a row group that uses {@link #TOP} alignment will
    204      * all return the same value when their {@link android.view.View#getTop()}
    205      * method is called.
    206      *
    207      * @see #setAlignmentMode(int)
    208      */
    209     public static final int ALIGN_BOUNDS = 0;
    210 
    211     /**
    212      * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
    213      * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS},
    214      * the bounds of each view are extended outwards, according
    215      * to their margins, before the edges of the resulting rectangle are aligned.
    216      * <p>
    217      * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode,
    218      * the quantity {@code top - layoutParams.topMargin} is the same for all children that
    219      * belong to a row group that uses {@link #TOP} alignment.
    220      *
    221      * @see #setAlignmentMode(int)
    222      */
    223     public static final int ALIGN_MARGINS = 1;
    224 
    225     // Misc constants
    226 
    227     static final int MAX_SIZE = 100000;
    228     static final int DEFAULT_CONTAINER_MARGIN = 0;
    229     static final int UNINITIALIZED_HASH = 0;
    230     static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName());
    231     static final Printer NO_PRINTER = new Printer() {
    232         @Override
    233         public void println(String x) {
    234         }
    235     };
    236 
    237     // Defaults
    238 
    239     private static final int DEFAULT_ORIENTATION = HORIZONTAL;
    240     private static final int DEFAULT_COUNT = UNDEFINED;
    241     private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
    242     static final boolean DEFAULT_ORDER_PRESERVED = true;
    243     private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
    244 
    245     // TypedArray indices
    246 
    247     private static final int ORIENTATION = R.styleable.GridLayout_orientation;
    248     private static final int ROW_COUNT = R.styleable.GridLayout_rowCount;
    249     private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount;
    250     private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins;
    251     private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode;
    252     private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved;
    253     private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved;
    254 
    255     // Instance variables
    256 
    257     final Axis mHorizontalAxis = new Axis(true);
    258     final Axis mVerticalAxis = new Axis(false);
    259     int mOrientation = DEFAULT_ORIENTATION;
    260     boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
    261     int mAlignmentMode = DEFAULT_ALIGNMENT_MODE;
    262     int mDefaultGap;
    263     int mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
    264     Printer mPrinter = LOG_PRINTER;
    265 
    266     // Constructors
    267 
    268     /**
    269      * {@inheritDoc}
    270      */
    271     public GridLayout(Context context, AttributeSet attrs, int defStyle) {
    272         super(context, attrs, defStyle);
    273         mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
    274         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout);
    275         try {
    276             setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
    277             setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
    278             setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION));
    279             setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS));
    280             setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE));
    281             setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
    282             setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
    283         } finally {
    284             a.recycle();
    285         }
    286     }
    287 
    288     /**
    289      * {@inheritDoc}
    290      */
    291     public GridLayout(Context context, AttributeSet attrs) {
    292         this(context, attrs, 0);
    293     }
    294 
    295     /**
    296      * {@inheritDoc}
    297      */
    298     public GridLayout(Context context) {
    299         //noinspection NullableProblems
    300         this(context, null);
    301     }
    302 
    303     // Implementation
    304 
    305     /**
    306      * Returns the current orientation.
    307      *
    308      * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
    309      *
    310      * @see #setOrientation(int)
    311      *
    312      * @attr name android:orientation
    313      */
    314     public int getOrientation() {
    315         return mOrientation;
    316     }
    317 
    318     /**
    319      *
    320      * GridLayout uses the orientation property for two purposes:
    321      * <ul>
    322      *  <li>
    323      *      To control the 'direction' in which default row/column indices are generated
    324      *      when they are not specified in a component's layout parameters.
    325      *  </li>
    326      *  <li>
    327      *      To control which axis should be processed first during the layout operation:
    328      *      when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first.
    329      *  </li>
    330      * </ul>
    331      *
    332      * The order in which axes are laid out is important if, for example, the height of
    333      * one of GridLayout's children is dependent on its width - and its width is, in turn,
    334      * dependent on the widths of other components.
    335      * <p>
    336      * If your layout contains a {@link android.widget.TextView} (or derivative:
    337      * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is
    338      * in multi-line mode (the default) it is normally best to leave GridLayout's
    339      * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of
    340      * deriving its height for a given width, but not the other way around.
    341      * <p>
    342      * Other than the effects above, orientation does not affect the actual layout operation of
    343      * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if
    344      * the height of the intended layout greatly exceeds its width.
    345      * <p>
    346      * The default value of this property is {@link #HORIZONTAL}.
    347      *
    348      * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
    349      *
    350      * @see #getOrientation()
    351      *
    352      * @attr name android:orientation
    353      */
    354     public void setOrientation(int orientation) {
    355         if (this.mOrientation != orientation) {
    356             this.mOrientation = orientation;
    357             invalidateStructure();
    358             requestLayout();
    359         }
    360     }
    361 
    362     /**
    363      * Returns the current number of rows. This is either the last value that was set
    364      * with {@link #setRowCount(int)} or, if no such value was set, the maximum
    365      * value of each the upper bounds defined in {@link LayoutParams#rowSpec}.
    366      *
    367      * @return the current number of rows
    368      *
    369      * @see #setRowCount(int)
    370      * @see LayoutParams#rowSpec
    371      *
    372      * @attr name android:rowCount
    373      */
    374     public int getRowCount() {
    375         return mVerticalAxis.getCount();
    376     }
    377 
    378     /**
    379      * RowCount is used only to generate default row/column indices when
    380      * they are not specified by a component's layout parameters.
    381      *
    382      * @param rowCount the number of rows
    383      *
    384      * @see #getRowCount()
    385      * @see LayoutParams#rowSpec
    386      *
    387      * @attr name android:rowCount
    388      */
    389     public void setRowCount(int rowCount) {
    390         mVerticalAxis.setCount(rowCount);
    391         invalidateStructure();
    392         requestLayout();
    393     }
    394 
    395     /**
    396      * Returns the current number of columns. This is either the last value that was set
    397      * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
    398      * value of each the upper bounds defined in {@link LayoutParams#columnSpec}.
    399      *
    400      * @return the current number of columns
    401      *
    402      * @see #setColumnCount(int)
    403      * @see LayoutParams#columnSpec
    404      *
    405      * @attr name android:columnCount
    406      */
    407     public int getColumnCount() {
    408         return mHorizontalAxis.getCount();
    409     }
    410 
    411     /**
    412      * ColumnCount is used only to generate default column/column indices when
    413      * they are not specified by a component's layout parameters.
    414      *
    415      * @param columnCount the number of columns.
    416      *
    417      * @see #getColumnCount()
    418      * @see LayoutParams#columnSpec
    419      *
    420      * @attr name android:columnCount
    421      */
    422     public void setColumnCount(int columnCount) {
    423         mHorizontalAxis.setCount(columnCount);
    424         invalidateStructure();
    425         requestLayout();
    426     }
    427 
    428     /**
    429      * Returns whether or not this GridLayout will allocate default margins when no
    430      * corresponding layout parameters are defined.
    431      *
    432      * @return {@code true} if default margins should be allocated
    433      *
    434      * @see #setUseDefaultMargins(boolean)
    435      *
    436      * @attr name android:useDefaultMargins
    437      */
    438     public boolean getUseDefaultMargins() {
    439         return mUseDefaultMargins;
    440     }
    441 
    442     /**
    443      * When {@code true}, GridLayout allocates default margins around children
    444      * based on the child's visual characteristics. Each of the
    445      * margins so defined may be independently overridden by an assignment
    446      * to the appropriate layout parameter.
    447      * <p>
    448      * When {@code false}, the default value of all margins is zero.
    449      * <p>
    450      * When setting to {@code true}, consider setting the value of the
    451      * {@link #setAlignmentMode(int) alignmentMode}
    452      * property to {@link #ALIGN_BOUNDS}.
    453      * <p>
    454      * The default value of this property is {@code false}.
    455      *
    456      * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
    457      *
    458      * @see #getUseDefaultMargins()
    459      * @see #setAlignmentMode(int)
    460      *
    461      * @see MarginLayoutParams#leftMargin
    462      * @see MarginLayoutParams#topMargin
    463      * @see MarginLayoutParams#rightMargin
    464      * @see MarginLayoutParams#bottomMargin
    465      *
    466      * @attr name android:useDefaultMargins
    467      */
    468     public void setUseDefaultMargins(boolean useDefaultMargins) {
    469         this.mUseDefaultMargins = useDefaultMargins;
    470         requestLayout();
    471     }
    472 
    473     /**
    474      * Returns the alignment mode.
    475      *
    476      * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
    477      *
    478      * @see #ALIGN_BOUNDS
    479      * @see #ALIGN_MARGINS
    480      *
    481      * @see #setAlignmentMode(int)
    482      *
    483      * @attr name android:alignmentMode
    484      */
    485     public int getAlignmentMode() {
    486         return mAlignmentMode;
    487     }
    488 
    489     /**
    490      * Sets the alignment mode to be used for all of the alignments between the
    491      * children of this container.
    492      * <p>
    493      * The default value of this property is {@link #ALIGN_MARGINS}.
    494      *
    495      * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
    496      *
    497      * @see #ALIGN_BOUNDS
    498      * @see #ALIGN_MARGINS
    499      *
    500      * @see #getAlignmentMode()
    501      *
    502      * @attr name android:alignmentMode
    503      */
    504     public void setAlignmentMode(int alignmentMode) {
    505         this.mAlignmentMode = alignmentMode;
    506         requestLayout();
    507     }
    508 
    509     /**
    510      * Returns whether or not row boundaries are ordered by their grid indices.
    511      *
    512      * @return {@code true} if row boundaries must appear in the order of their indices,
    513      *         {@code false} otherwise
    514      *
    515      * @see #setRowOrderPreserved(boolean)
    516      *
    517      * @attr name android:rowOrderPreserved
    518      */
    519     public boolean isRowOrderPreserved() {
    520         return mVerticalAxis.isOrderPreserved();
    521     }
    522 
    523     /**
    524      * When this property is {@code true}, GridLayout is forced to place the row boundaries
    525      * so that their associated grid indices are in ascending order in the view.
    526      * <p>
    527      * When this property is {@code false} GridLayout is at liberty to place the vertical row
    528      * boundaries in whatever order best fits the given constraints.
    529      * <p>
    530      * The default value of this property is {@code true}.
    531 
    532      * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
    533      *        of row boundaries
    534      *
    535      * @see #isRowOrderPreserved()
    536      *
    537      * @attr name android:rowOrderPreserved
    538      */
    539     public void setRowOrderPreserved(boolean rowOrderPreserved) {
    540         mVerticalAxis.setOrderPreserved(rowOrderPreserved);
    541         invalidateStructure();
    542         requestLayout();
    543     }
    544 
    545     /**
    546      * Returns whether or not column boundaries are ordered by their grid indices.
    547      *
    548      * @return {@code true} if column boundaries must appear in the order of their indices,
    549      *         {@code false} otherwise
    550      *
    551      * @see #setColumnOrderPreserved(boolean)
    552      *
    553      * @attr name android:columnOrderPreserved
    554      */
    555     public boolean isColumnOrderPreserved() {
    556         return mHorizontalAxis.isOrderPreserved();
    557     }
    558 
    559     /**
    560      * When this property is {@code true}, GridLayout is forced to place the column boundaries
    561      * so that their associated grid indices are in ascending order in the view.
    562      * <p>
    563      * When this property is {@code false} GridLayout is at liberty to place the horizontal column
    564      * boundaries in whatever order best fits the given constraints.
    565      * <p>
    566      * The default value of this property is {@code true}.
    567      *
    568      * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
    569      *        of column boundaries.
    570      *
    571      * @see #isColumnOrderPreserved()
    572      *
    573      * @attr name android:columnOrderPreserved
    574      */
    575     public void setColumnOrderPreserved(boolean columnOrderPreserved) {
    576         mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
    577         invalidateStructure();
    578         requestLayout();
    579     }
    580 
    581     /**
    582      * Return the printer that will log diagnostics from this layout.
    583      *
    584      * @see #setPrinter(android.util.Printer)
    585      *
    586      * @return the printer associated with this view
    587      */
    588     public Printer getPrinter() {
    589         return mPrinter;
    590     }
    591 
    592     /**
    593      * Set the printer that will log diagnostics from this layout.
    594      * The default value is created by {@link android.util.LogPrinter}.
    595      *
    596      * @param printer the printer associated with this layout
    597      *
    598      * @see #getPrinter()
    599      */
    600     public void setPrinter(Printer printer) {
    601         this.mPrinter = (printer == null) ? NO_PRINTER : printer;
    602     }
    603 
    604     // Static utility methods
    605 
    606     static int max2(int[] a, int valueIfEmpty) {
    607         int result = valueIfEmpty;
    608         for (int i = 0, N = a.length; i < N; i++) {
    609             result = Math.max(result, a[i]);
    610         }
    611         return result;
    612     }
    613 
    614     @SuppressWarnings("unchecked")
    615     static <T> T[] append(T[] a, T[] b) {
    616         T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
    617         System.arraycopy(a, 0, result, 0, a.length);
    618         System.arraycopy(b, 0, result, a.length, b.length);
    619         return result;
    620     }
    621 
    622     static Alignment getAlignment(int gravity, boolean horizontal) {
    623         int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
    624         int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
    625         int flags = (gravity & mask) >> shift;
    626         switch (flags) {
    627             case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
    628                 return horizontal ? LEFT : TOP;
    629             case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
    630                 return horizontal ? RIGHT : BOTTOM;
    631             case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
    632                 return FILL;
    633             case AXIS_SPECIFIED:
    634                 return CENTER;
    635             case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | GravityCompat.RELATIVE_LAYOUT_DIRECTION):
    636                 return START;
    637             case (AXIS_SPECIFIED | AXIS_PULL_AFTER | GravityCompat.RELATIVE_LAYOUT_DIRECTION):
    638                 return END;
    639             default:
    640                 return UNDEFINED_ALIGNMENT;
    641         }
    642     }
    643 
    644     /** @noinspection UnusedParameters*/
    645     private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
    646         if (c.getClass() == Space.class
    647                 || c.getClass() == android.widget.Space.class) {
    648             return 0;
    649         }
    650         return mDefaultGap / 2;
    651     }
    652 
    653     private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
    654         return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
    655     }
    656 
    657     private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
    658         if (!mUseDefaultMargins) {
    659             return 0;
    660         }
    661         Spec spec = horizontal ? p.columnSpec : p.rowSpec;
    662         Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
    663         Interval span = spec.span;
    664         boolean leading1 = (horizontal && isLayoutRtlCompat()) ? !leading : leading;
    665         boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
    666 
    667         return getDefaultMargin(c, isAtEdge, horizontal, leading);
    668     }
    669 
    670     int getMargin1(View view, boolean horizontal, boolean leading) {
    671         LayoutParams lp = getLayoutParams(view);
    672         int margin = horizontal ?
    673                 (leading ? lp.leftMargin : lp.rightMargin) :
    674                 (leading ? lp.topMargin : lp.bottomMargin);
    675         return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin;
    676     }
    677 
    678     private boolean isLayoutRtlCompat() {
    679         return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
    680     }
    681 
    682     private int getMargin(View view, boolean horizontal, boolean leading) {
    683         if (mAlignmentMode == ALIGN_MARGINS) {
    684             return getMargin1(view, horizontal, leading);
    685         } else {
    686             Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
    687             int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
    688             LayoutParams lp = getLayoutParams(view);
    689             Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
    690             int index = leading ? spec.span.min : spec.span.max;
    691             return margins[index];
    692         }
    693     }
    694 
    695     private int getTotalMargin(View child, boolean horizontal) {
    696         return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
    697     }
    698 
    699     private static boolean fits(int[] a, int value, int start, int end) {
    700         if (end > a.length) {
    701             return false;
    702         }
    703         for (int i = start; i < end; i++) {
    704             if (a[i] > value) {
    705                 return false;
    706             }
    707         }
    708         return true;
    709     }
    710 
    711     private static void procrusteanFill(int[] a, int start, int end, int value) {
    712         int length = a.length;
    713         Arrays.fill(a, Math.min(start, length), Math.min(end, length), value);
    714     }
    715 
    716     private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) {
    717         lp.setRowSpecSpan(new Interval(row, row + rowSpan));
    718         lp.setColumnSpecSpan(new Interval(col, col + colSpan));
    719     }
    720 
    721     // Logic to avert infinite loops by ensuring that the cells can be placed somewhere.
    722     private static int clip(Interval minorRange, boolean minorWasDefined, int count) {
    723         int size = minorRange.size();
    724         if (count == 0) {
    725             return size;
    726         }
    727         int min = minorWasDefined ? min(minorRange.min, count) : 0;
    728         return min(size, count - min);
    729     }
    730 
    731     // install default indices for cells that don't define them
    732     private void validateLayoutParams() {
    733         final boolean horizontal = (mOrientation == HORIZONTAL);
    734         final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
    735         final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
    736 
    737         int major = 0;
    738         int minor = 0;
    739         int[] maxSizes = new int[count];
    740 
    741         for (int i = 0, N = getChildCount(); i < N; i++) {
    742             LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
    743 
    744             final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
    745             final Interval majorRange = majorSpec.span;
    746             final boolean majorWasDefined = majorSpec.startDefined;
    747             final int majorSpan = majorRange.size();
    748             if (majorWasDefined) {
    749                 major = majorRange.min;
    750             }
    751 
    752             final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
    753             final Interval minorRange = minorSpec.span;
    754             final boolean minorWasDefined = minorSpec.startDefined;
    755             final int minorSpan = clip(minorRange, minorWasDefined, count);
    756             if (minorWasDefined) {
    757                 minor = minorRange.min;
    758             }
    759 
    760             if (count != 0) {
    761                 // Find suitable row/col values when at least one is undefined.
    762                 if (!majorWasDefined || !minorWasDefined) {
    763                     while (!fits(maxSizes, major, minor, minor + minorSpan)) {
    764                         if (minorWasDefined) {
    765                             major++;
    766                         } else {
    767                             if (minor + minorSpan <= count) {
    768                                 minor++;
    769                             } else {
    770                                 minor = 0;
    771                                 major++;
    772                             }
    773                         }
    774                     }
    775                 }
    776                 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan);
    777             }
    778 
    779             if (horizontal) {
    780                 setCellGroup(lp, major, majorSpan, minor, minorSpan);
    781             } else {
    782                 setCellGroup(lp, minor, minorSpan, major, majorSpan);
    783             }
    784 
    785             minor = minor + minorSpan;
    786         }
    787     }
    788 
    789     private void invalidateStructure() {
    790         mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
    791         if (mHorizontalAxis != null) mHorizontalAxis.invalidateStructure();
    792         if (mVerticalAxis != null) mVerticalAxis.invalidateStructure();
    793         // This can end up being done twice. Better twice than not at all.
    794         invalidateValues();
    795     }
    796 
    797     private void invalidateValues() {
    798         // Need null check because requestLayout() is called in View's initializer,
    799         // before we are set up.
    800         if (mHorizontalAxis != null && mVerticalAxis != null) {
    801             mHorizontalAxis.invalidateValues();
    802             mVerticalAxis.invalidateValues();
    803         }
    804     }
    805 
    806     final LayoutParams getLayoutParams(View c) {
    807         return (LayoutParams) c.getLayoutParams();
    808     }
    809 
    810     static void handleInvalidParams(String msg) {
    811         throw new IllegalArgumentException(msg + ". ");
    812     }
    813 
    814     private void checkLayoutParams(LayoutParams lp, boolean horizontal) {
    815         String groupName = horizontal ? "column" : "row";
    816         Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
    817         Interval span = spec.span;
    818         if (span.min != UNDEFINED && span.min < 0) {
    819             handleInvalidParams(groupName + " indices must be positive");
    820         }
    821         Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
    822         int count = axis.definedCount;
    823         if (count != UNDEFINED) {
    824             if (span.max > count) {
    825                 handleInvalidParams(groupName +
    826                         " indices (start + span) mustn't exceed the " + groupName + " count");
    827             }
    828             if (span.size() > count) {
    829                 handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count");
    830             }
    831         }
    832     }
    833 
    834     @Override
    835     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    836         if (!(p instanceof LayoutParams)) {
    837             return false;
    838         }
    839         LayoutParams lp = (LayoutParams) p;
    840 
    841         checkLayoutParams(lp, true);
    842         checkLayoutParams(lp, false);
    843 
    844         return true;
    845     }
    846 
    847     @Override
    848     protected LayoutParams generateDefaultLayoutParams() {
    849         return new LayoutParams();
    850     }
    851 
    852     @Override
    853     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    854         return new LayoutParams(getContext(), attrs);
    855     }
    856 
    857     @Override
    858     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
    859         if (lp instanceof LayoutParams) {
    860             return new LayoutParams((LayoutParams) lp);
    861         } else if (lp instanceof MarginLayoutParams) {
    862             return new LayoutParams((MarginLayoutParams) lp);
    863         } else {
    864             return new LayoutParams(lp);
    865         }
    866     }
    867 
    868     // Draw grid
    869 
    870     private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
    871         if (isLayoutRtlCompat()) {
    872             int width = getWidth();
    873             graphics.drawLine(width - x1, y1, width - x2, y2, paint);
    874         } else {
    875             graphics.drawLine(x1, y1, x2, y2, paint);
    876         }
    877     }
    878 
    879     private int computeLayoutParamsHashCode() {
    880         int result = 1;
    881         for (int i = 0, N = getChildCount(); i < N; i++) {
    882             View c = getChildAt(i);
    883             if (c.getVisibility() == View.GONE) continue;
    884             LayoutParams lp = (LayoutParams) c.getLayoutParams();
    885             result = 31 * result + lp.hashCode();
    886         }
    887         return result;
    888     }
    889 
    890     private void consistencyCheck() {
    891         if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) {
    892             validateLayoutParams();
    893             mLastLayoutParamsHashCode = computeLayoutParamsHashCode();
    894         } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
    895             mPrinter.println("The fields of some layout parameters were modified in between "
    896                     + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
    897             invalidateStructure();
    898             consistencyCheck();
    899         }
    900     }
    901 
    902     // Measurement
    903 
    904     // Note: padding has already been removed from the supplied specs
    905     private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
    906                                           int childWidth, int childHeight) {
    907         int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
    908                 getTotalMargin(child, true), childWidth);
    909         int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
    910                 getTotalMargin(child, false), childHeight);
    911         child.measure(childWidthSpec, childHeightSpec);
    912     }
    913 
    914     // Note: padding has already been removed from the supplied specs
    915     private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
    916         for (int i = 0, N = getChildCount(); i < N; i++) {
    917             View c = getChildAt(i);
    918             if (c.getVisibility() == View.GONE) continue;
    919             LayoutParams lp = getLayoutParams(c);
    920             if (firstPass) {
    921                 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
    922             } else {
    923                 boolean horizontal = (mOrientation == HORIZONTAL);
    924                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
    925                 if (spec.getAbsoluteAlignment(horizontal) == FILL) {
    926                     Interval span = spec.span;
    927                     Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
    928                     int[] locations = axis.getLocations();
    929                     int cellSize = locations[span.max] - locations[span.min];
    930                     int viewSize = cellSize - getTotalMargin(c, horizontal);
    931                     if (horizontal) {
    932                         measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);
    933                     } else {
    934                         measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);
    935                     }
    936                 }
    937             }
    938         }
    939     }
    940 
    941     static int adjust(int measureSpec, int delta) {
    942         return makeMeasureSpec(
    943                 MeasureSpec.getSize(measureSpec + delta),  MeasureSpec.getMode(measureSpec));
    944     }
    945 
    946     @Override
    947     protected void onMeasure(int widthSpec, int heightSpec) {
    948         consistencyCheck();
    949 
    950         /** If we have been called by {@link View#measure(int, int)}, one of width or height
    951          *  is  likely to have changed. We must invalidate if so. */
    952         invalidateValues();
    953 
    954         int hPadding = getPaddingLeft() + getPaddingRight();
    955         int vPadding = getPaddingTop()  + getPaddingBottom();
    956 
    957         int widthSpecSansPadding =  adjust( widthSpec, -hPadding);
    958         int heightSpecSansPadding = adjust(heightSpec, -vPadding);
    959 
    960         measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);
    961 
    962         int widthSansPadding;
    963         int heightSansPadding;
    964 
    965         // Use the orientation property to decide which axis should be laid out first.
    966         if (mOrientation == HORIZONTAL) {
    967             widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
    968             measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
    969             heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
    970         } else {
    971             heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
    972             measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
    973             widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
    974         }
    975 
    976         int measuredWidth  = Math.max(widthSansPadding  + hPadding, getSuggestedMinimumWidth());
    977         int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight());
    978 
    979         setMeasuredDimension(
    980                 View.resolveSizeAndState(measuredWidth, widthSpec, 0),
    981                 View.resolveSizeAndState(measuredHeight, heightSpec, 0));
    982     }
    983 
    984     private int getMeasurement(View c, boolean horizontal) {
    985         return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
    986     }
    987 
    988     final int getMeasurementIncludingMargin(View c, boolean horizontal) {
    989         if (c.getVisibility() == View.GONE) {
    990             return 0;
    991         }
    992         return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
    993     }
    994 
    995     @Override
    996     public void requestLayout() {
    997         super.requestLayout();
    998         invalidateStructure();
    999     }
   1000 
   1001     // Layout container
   1002 
   1003     /**
   1004      * {@inheritDoc}
   1005      */
   1006     /*
   1007      The layout operation is implemented by delegating the heavy lifting to the
   1008      to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
   1009      Together they compute the locations of the vertical and horizontal lines of
   1010      the grid (respectively!).
   1011 
   1012      This method is then left with the simpler task of applying margins, gravity
   1013      and sizing to each child view and then placing it in its cell.
   1014      */
   1015     @Override
   1016     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   1017         consistencyCheck();
   1018 
   1019         int targetWidth = right - left;
   1020         int targetHeight = bottom - top;
   1021 
   1022         int paddingLeft = getPaddingLeft();
   1023         int paddingTop = getPaddingTop();
   1024         int paddingRight = getPaddingRight();
   1025         int paddingBottom = getPaddingBottom();
   1026 
   1027         mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
   1028         mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
   1029 
   1030         int[] hLocations = mHorizontalAxis.getLocations();
   1031         int[] vLocations = mVerticalAxis.getLocations();
   1032 
   1033         for (int i = 0, N = getChildCount(); i < N; i++) {
   1034             View c = getChildAt(i);
   1035             if (c.getVisibility() == View.GONE) continue;
   1036             LayoutParams lp = getLayoutParams(c);
   1037             Spec columnSpec = lp.columnSpec;
   1038             Spec rowSpec = lp.rowSpec;
   1039 
   1040             Interval colSpan = columnSpec.span;
   1041             Interval rowSpan = rowSpec.span;
   1042 
   1043             int x1 = hLocations[colSpan.min];
   1044             int y1 = vLocations[rowSpan.min];
   1045 
   1046             int x2 = hLocations[colSpan.max];
   1047             int y2 = vLocations[rowSpan.max];
   1048 
   1049             int cellWidth = x2 - x1;
   1050             int cellHeight = y2 - y1;
   1051 
   1052             int pWidth = getMeasurement(c, true);
   1053             int pHeight = getMeasurement(c, false);
   1054 
   1055             Alignment hAlign = columnSpec.getAbsoluteAlignment(true);
   1056             Alignment vAlign = rowSpec.getAbsoluteAlignment(false);
   1057 
   1058             Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i);
   1059             Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i);
   1060 
   1061             // Gravity offsets: the location of the alignment group relative to its cell group.
   1062             int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true));
   1063             int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true));
   1064 
   1065             int leftMargin = getMargin(c, true, true);
   1066             int topMargin = getMargin(c, false, true);
   1067             int rightMargin = getMargin(c, true, false);
   1068             int bottomMargin = getMargin(c, false, false);
   1069 
   1070             int sumMarginsX = leftMargin + rightMargin;
   1071             int sumMarginsY = topMargin + bottomMargin;
   1072 
   1073             // Alignment offsets: the location of the view relative to its alignment group.
   1074             int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
   1075             int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
   1076 
   1077             int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
   1078             int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
   1079 
   1080             int dx = x1 + gravityOffsetX + alignmentOffsetX;
   1081 
   1082             int cx = !isLayoutRtlCompat() ? paddingLeft + leftMargin + dx :
   1083                     targetWidth - width - paddingRight - rightMargin - dx;
   1084             int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
   1085 
   1086             if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
   1087                 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
   1088             }
   1089             c.layout(cx, cy, cx + width, cy + height);
   1090         }
   1091     }
   1092 
   1093     // Inner classes
   1094 
   1095     /*
   1096      This internal class houses the algorithm for computing the locations of grid lines;
   1097      along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
   1098      distinguished by the "horizontal" flag which is true for the horizontal axis and false
   1099      for the vertical one.
   1100      */
   1101     final class Axis {
   1102         static final int NEW = 0;
   1103         static final int PENDING = 1;
   1104         static final int COMPLETE = 2;
   1105 
   1106         public final boolean horizontal;
   1107 
   1108         public int definedCount = UNDEFINED;
   1109         private int maxIndex = UNDEFINED;
   1110 
   1111         PackedMap<Spec, Bounds> groupBounds;
   1112         public boolean groupBoundsValid = false;
   1113 
   1114         PackedMap<Interval, MutableInt> forwardLinks;
   1115         public boolean forwardLinksValid = false;
   1116 
   1117         PackedMap<Interval, MutableInt> backwardLinks;
   1118         public boolean backwardLinksValid = false;
   1119 
   1120         public int[] leadingMargins;
   1121         public boolean leadingMarginsValid = false;
   1122 
   1123         public int[] trailingMargins;
   1124         public boolean trailingMarginsValid = false;
   1125 
   1126         public Arc[] arcs;
   1127         public boolean arcsValid = false;
   1128 
   1129         public int[] locations;
   1130         public boolean locationsValid = false;
   1131 
   1132         public boolean hasWeights;
   1133         public boolean hasWeightsValid = false;
   1134         public int[] deltas;
   1135 
   1136         boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
   1137 
   1138         private MutableInt parentMin = new MutableInt(0);
   1139         private MutableInt parentMax = new MutableInt(-MAX_SIZE);
   1140 
   1141         Axis(boolean horizontal) {
   1142             this.horizontal = horizontal;
   1143         }
   1144 
   1145         private int calculateMaxIndex() {
   1146             // the number Integer.MIN_VALUE + 1 comes up in undefined cells
   1147             int result = -1;
   1148             for (int i = 0, N = getChildCount(); i < N; i++) {
   1149                 View c = getChildAt(i);
   1150                 LayoutParams params = getLayoutParams(c);
   1151                 Spec spec = horizontal ? params.columnSpec : params.rowSpec;
   1152                 Interval span = spec.span;
   1153                 result = max(result, span.min);
   1154                 result = max(result, span.max);
   1155                 result = max(result, span.size());
   1156             }
   1157             return result == -1 ? UNDEFINED : result;
   1158         }
   1159 
   1160         private int getMaxIndex() {
   1161             if (maxIndex == UNDEFINED) {
   1162                 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children
   1163             }
   1164             return maxIndex;
   1165         }
   1166 
   1167         public int getCount() {
   1168             return max(definedCount, getMaxIndex());
   1169         }
   1170 
   1171         public void setCount(int count) {
   1172             if (count != UNDEFINED && count < getMaxIndex()) {
   1173                 handleInvalidParams((horizontal ? "column" : "row") +
   1174                         "Count must be greater than or equal to the maximum of all grid indices " +
   1175                         "(and spans) defined in the LayoutParams of each child");
   1176             }
   1177             this.definedCount = count;
   1178         }
   1179 
   1180         public boolean isOrderPreserved() {
   1181             return orderPreserved;
   1182         }
   1183 
   1184         public void setOrderPreserved(boolean orderPreserved) {
   1185             this.orderPreserved = orderPreserved;
   1186             invalidateStructure();
   1187         }
   1188 
   1189         private PackedMap<Spec, Bounds> createGroupBounds() {
   1190             Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class);
   1191             for (int i = 0, N = getChildCount(); i < N; i++) {
   1192                 View c = getChildAt(i);
   1193                 // we must include views that are GONE here, see introductory javadoc
   1194                 LayoutParams lp = getLayoutParams(c);
   1195                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
   1196                 Bounds bounds = spec.getAbsoluteAlignment(horizontal).getBounds();
   1197                 assoc.put(spec, bounds);
   1198             }
   1199             return assoc.pack();
   1200         }
   1201 
   1202         private void computeGroupBounds() {
   1203             Bounds[] values = groupBounds.values;
   1204             for (int i = 0; i < values.length; i++) {
   1205                 values[i].reset();
   1206             }
   1207             for (int i = 0, N = getChildCount(); i < N; i++) {
   1208                 View c = getChildAt(i);
   1209                 // we must include views that are GONE here, see introductory javadoc
   1210                 LayoutParams lp = getLayoutParams(c);
   1211                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
   1212                 int size = getMeasurementIncludingMargin(c, horizontal) +
   1213                                 ((spec.weight == 0) ? 0 : getDeltas()[i]);
   1214                 groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
   1215             }
   1216         }
   1217 
   1218         public PackedMap<Spec, Bounds> getGroupBounds() {
   1219             if (groupBounds == null) {
   1220                 groupBounds = createGroupBounds();
   1221             }
   1222             if (!groupBoundsValid) {
   1223                 computeGroupBounds();
   1224                 groupBoundsValid = true;
   1225             }
   1226             return groupBounds;
   1227         }
   1228 
   1229         // Add values computed by alignment - taking the max of all alignments in each span
   1230         private PackedMap<Interval, MutableInt> createLinks(boolean min) {
   1231             Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class);
   1232             Spec[] keys = getGroupBounds().keys;
   1233             for (int i = 0, N = keys.length; i < N; i++) {
   1234                 Interval span = min ? keys[i].span : keys[i].span.inverse();
   1235                 result.put(span, new MutableInt());
   1236             }
   1237             return result.pack();
   1238         }
   1239 
   1240         private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) {
   1241             MutableInt[] spans = links.values;
   1242             for (int i = 0; i < spans.length; i++) {
   1243                 spans[i].reset();
   1244             }
   1245 
   1246             // Use getter to trigger a re-evaluation
   1247             Bounds[] bounds = getGroupBounds().values;
   1248             for (int i = 0; i < bounds.length; i++) {
   1249                 int size = bounds[i].size(min);
   1250                 MutableInt valueHolder = links.getValue(i);
   1251                 // this effectively takes the max() of the minima and the min() of the maxima
   1252                 valueHolder.value = max(valueHolder.value, min ? size : -size);
   1253             }
   1254         }
   1255 
   1256         private PackedMap<Interval, MutableInt> getForwardLinks() {
   1257             if (forwardLinks == null) {
   1258                 forwardLinks = createLinks(true);
   1259             }
   1260             if (!forwardLinksValid) {
   1261                 computeLinks(forwardLinks, true);
   1262                 forwardLinksValid = true;
   1263             }
   1264             return forwardLinks;
   1265         }
   1266 
   1267         private PackedMap<Interval, MutableInt> getBackwardLinks() {
   1268             if (backwardLinks == null) {
   1269                 backwardLinks = createLinks(false);
   1270             }
   1271             if (!backwardLinksValid) {
   1272                 computeLinks(backwardLinks, false);
   1273                 backwardLinksValid = true;
   1274             }
   1275             return backwardLinks;
   1276         }
   1277 
   1278         private void include(List<Arc> arcs, Interval key, MutableInt size,
   1279                              boolean ignoreIfAlreadyPresent) {
   1280             /*
   1281             Remove self referential links.
   1282             These appear:
   1283                 . as parental constraints when GridLayout has no children
   1284                 . when components have been marked as GONE
   1285             */
   1286             if (key.size() == 0) {
   1287                 return;
   1288             }
   1289             // this bit below should really be computed outside here -
   1290             // its just to stop default (row/col > 0) constraints obliterating valid entries
   1291             if (ignoreIfAlreadyPresent) {
   1292                 for (Arc arc : arcs) {
   1293                     Interval span = arc.span;
   1294                     if (span.equals(key)) {
   1295                         return;
   1296                     }
   1297                 }
   1298             }
   1299             arcs.add(new Arc(key, size));
   1300         }
   1301 
   1302         private void include(List<Arc> arcs, Interval key, MutableInt size) {
   1303             include(arcs, key, size, true);
   1304         }
   1305 
   1306         // Group arcs by their first vertex, returning an array of arrays.
   1307         // This is linear in the number of arcs.
   1308         Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
   1309             int N = getCount() + 1; // the number of vertices
   1310             Arc[][] result = new Arc[N][];
   1311             int[] sizes = new int[N];
   1312             for (Arc arc : arcs) {
   1313                 sizes[arc.span.min]++;
   1314             }
   1315             for (int i = 0; i < sizes.length; i++) {
   1316                 result[i] = new Arc[sizes[i]];
   1317             }
   1318             // reuse the sizes array to hold the current last elements as we insert each arc
   1319             Arrays.fill(sizes, 0);
   1320             for (Arc arc : arcs) {
   1321                 int i = arc.span.min;
   1322                 result[i][sizes[i]++] = arc;
   1323             }
   1324 
   1325             return result;
   1326         }
   1327 
   1328         private Arc[] topologicalSort(final Arc[] arcs) {
   1329             return new Object() {
   1330                 Arc[] result = new Arc[arcs.length];
   1331                 int cursor = result.length - 1;
   1332                 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs);
   1333                 int[] visited = new int[getCount() + 1];
   1334 
   1335                 void walk(int loc) {
   1336                     switch (visited[loc]) {
   1337                         case NEW: {
   1338                             visited[loc] = PENDING;
   1339                             for (Arc arc : arcsByVertex[loc]) {
   1340                                 walk(arc.span.max);
   1341                                 result[cursor--] = arc;
   1342                             }
   1343                             visited[loc] = COMPLETE;
   1344                             break;
   1345                         }
   1346                         case PENDING: {
   1347                             // le singe est dans l'arbre
   1348                             assert false;
   1349                             break;
   1350                         }
   1351                         case COMPLETE: {
   1352                             break;
   1353                         }
   1354                     }
   1355                 }
   1356 
   1357                 Arc[] sort() {
   1358                     for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) {
   1359                         walk(loc);
   1360                     }
   1361                     assert cursor == -1;
   1362                     return result;
   1363                 }
   1364             }.sort();
   1365         }
   1366 
   1367         private Arc[] topologicalSort(List<Arc> arcs) {
   1368             return topologicalSort(arcs.toArray(new Arc[arcs.size()]));
   1369         }
   1370 
   1371         private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) {
   1372             for (int i = 0; i < links.keys.length; i++) {
   1373                 Interval key = links.keys[i];
   1374                 include(result, key, links.values[i], false);
   1375             }
   1376         }
   1377 
   1378         private Arc[] createArcs() {
   1379             List<Arc> mins = new ArrayList<Arc>();
   1380             List<Arc> maxs = new ArrayList<Arc>();
   1381 
   1382             // Add the minimum values from the components.
   1383             addComponentSizes(mins, getForwardLinks());
   1384             // Add the maximum values from the components.
   1385             addComponentSizes(maxs, getBackwardLinks());
   1386 
   1387             // Add ordering constraints to prevent row/col sizes from going negative
   1388             if (orderPreserved) {
   1389                 // Add a constraint for every row/col
   1390                 for (int i = 0; i < getCount(); i++) {
   1391                     include(mins, new Interval(i, i + 1), new MutableInt(0));
   1392                 }
   1393             }
   1394 
   1395             // Add the container constraints. Use the version of include that allows
   1396             // duplicate entries in case a child spans the entire grid.
   1397             int N = getCount();
   1398             include(mins, new Interval(0, N), parentMin, false);
   1399             include(maxs, new Interval(N, 0), parentMax, false);
   1400 
   1401             // Sort
   1402             Arc[] sMins = topologicalSort(mins);
   1403             Arc[] sMaxs = topologicalSort(maxs);
   1404 
   1405             return append(sMins, sMaxs);
   1406         }
   1407 
   1408         private void computeArcs() {
   1409             // getting the links validates the values that are shared by the arc list
   1410             getForwardLinks();
   1411             getBackwardLinks();
   1412         }
   1413 
   1414         public Arc[] getArcs() {
   1415             if (arcs == null) {
   1416                 arcs = createArcs();
   1417             }
   1418             if (!arcsValid) {
   1419                 computeArcs();
   1420                 arcsValid = true;
   1421             }
   1422             return arcs;
   1423         }
   1424 
   1425         private boolean relax(int[] locations, Arc entry) {
   1426             if (!entry.valid) {
   1427                 return false;
   1428             }
   1429             Interval span = entry.span;
   1430             int u = span.min;
   1431             int v = span.max;
   1432             int value = entry.value.value;
   1433             int candidate = locations[u] + value;
   1434             if (candidate > locations[v]) {
   1435                 locations[v] = candidate;
   1436                 return true;
   1437             }
   1438             return false;
   1439         }
   1440 
   1441         private void init(int[] locations) {
   1442             Arrays.fill(locations, 0);
   1443         }
   1444 
   1445         private String arcsToString(List<Arc> arcs) {
   1446             String var = horizontal ? "x" : "y";
   1447             StringBuilder result = new StringBuilder();
   1448             boolean first = true;
   1449             for (Arc arc : arcs) {
   1450                 if (first) {
   1451                     first = false;
   1452                 } else {
   1453                     result = result.append(", ");
   1454                 }
   1455                 int src = arc.span.min;
   1456                 int dst = arc.span.max;
   1457                 int value = arc.value.value;
   1458                 result.append((src < dst) ?
   1459                         var + dst + "-" + var + src + ">=" + value :
   1460                         var + src + "-" + var + dst + "<=" + -value);
   1461 
   1462             }
   1463             return result.toString();
   1464         }
   1465 
   1466         private void logError(String axisName, Arc[] arcs, boolean[] culprits0) {
   1467             List<Arc> culprits = new ArrayList<Arc>();
   1468             List<Arc> removed = new ArrayList<Arc>();
   1469             for (int c = 0; c < arcs.length; c++) {
   1470                 Arc arc = arcs[c];
   1471                 if (culprits0[c]) {
   1472                     culprits.add(arc);
   1473                 }
   1474                 if (!arc.valid) {
   1475                     removed.add(arc);
   1476                 }
   1477             }
   1478             mPrinter.println(axisName + " constraints: " + arcsToString(culprits) +
   1479                     " are inconsistent; permanently removing: " + arcsToString(removed) + ". ");
   1480         }
   1481 
   1482         /*
   1483         Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
   1484 
   1485         GridLayout converts its requirements into a system of linear constraints of the
   1486         form:
   1487 
   1488         x[i] - x[j] < a[k]
   1489 
   1490         Where the x[i] are variables and the a[k] are constants.
   1491 
   1492         For example, if the variables were instead labeled x, y, z we might have:
   1493 
   1494             x - y < 17
   1495             y - z < 23
   1496             z - x < 42
   1497 
   1498         This is a special case of the Linear Programming problem that is, in turn,
   1499         equivalent to the single-source shortest paths problem on a digraph, for
   1500         which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
   1501         */
   1502         private boolean solve(Arc[] arcs, int[] locations) {
   1503             return solve(arcs, locations, true);
   1504         }
   1505 
   1506         private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) {
   1507             String axisName = horizontal ? "horizontal" : "vertical";
   1508             int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
   1509             boolean[] originalCulprits = null;
   1510 
   1511             for (int p = 0; p < arcs.length; p++) {
   1512                 init(locations);
   1513 
   1514                 // We take one extra pass over traditional Bellman-Ford (and omit their final step)
   1515                 for (int i = 0; i < N; i++) {
   1516                     boolean changed = false;
   1517                     for (int j = 0, length = arcs.length; j < length; j++) {
   1518                         changed |= relax(locations, arcs[j]);
   1519                     }
   1520                     if (!changed) {
   1521                         if (originalCulprits != null) {
   1522                             logError(axisName, arcs, originalCulprits);
   1523                         }
   1524                         return true;
   1525                     }
   1526                 }
   1527 
   1528                 if (!modifyOnError) {
   1529                     return false; // cannot solve with these constraints
   1530                 }
   1531 
   1532                 boolean[] culprits = new boolean[arcs.length];
   1533                 for (int i = 0; i < N; i++) {
   1534                     for (int j = 0, length = arcs.length; j < length; j++) {
   1535                         culprits[j] |= relax(locations, arcs[j]);
   1536                     }
   1537                 }
   1538 
   1539                 if (p == 0) {
   1540                     originalCulprits = culprits;
   1541                 }
   1542 
   1543                 for (int i = 0; i < arcs.length; i++) {
   1544                     if (culprits[i]) {
   1545                         Arc arc = arcs[i];
   1546                         // Only remove max values, min values alone cannot be inconsistent
   1547                         if (arc.span.min < arc.span.max) {
   1548                             continue;
   1549                         }
   1550                         arc.valid = false;
   1551                         break;
   1552                     }
   1553                 }
   1554             }
   1555             return true;
   1556         }
   1557 
   1558         private void computeMargins(boolean leading) {
   1559             int[] margins = leading ? leadingMargins : trailingMargins;
   1560             for (int i = 0, N = getChildCount(); i < N; i++) {
   1561                 View c = getChildAt(i);
   1562                 if (c.getVisibility() == View.GONE) continue;
   1563                 LayoutParams lp = getLayoutParams(c);
   1564                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
   1565                 Interval span = spec.span;
   1566                 int index = leading ? span.min : span.max;
   1567                 margins[index] = max(margins[index], getMargin1(c, horizontal, leading));
   1568             }
   1569         }
   1570 
   1571         // External entry points
   1572 
   1573         public int[] getLeadingMargins() {
   1574             if (leadingMargins == null) {
   1575                 leadingMargins = new int[getCount() + 1];
   1576             }
   1577             if (!leadingMarginsValid) {
   1578                 computeMargins(true);
   1579                 leadingMarginsValid = true;
   1580             }
   1581             return leadingMargins;
   1582         }
   1583 
   1584         public int[] getTrailingMargins() {
   1585             if (trailingMargins == null) {
   1586                 trailingMargins = new int[getCount() + 1];
   1587             }
   1588             if (!trailingMarginsValid) {
   1589                 computeMargins(false);
   1590                 trailingMarginsValid = true;
   1591             }
   1592             return trailingMargins;
   1593         }
   1594 
   1595         private boolean solve(int[] a) {
   1596             return solve(getArcs(), a);
   1597         }
   1598 
   1599         private boolean computeHasWeights() {
   1600             for (int i = 0, N = getChildCount(); i < N; i++) {
   1601                 final View child = getChildAt(i);
   1602                 if (child.getVisibility() == View.GONE) {
   1603                     continue;
   1604                 }
   1605                 LayoutParams lp = getLayoutParams(child);
   1606                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
   1607                 if (spec.weight != 0) {
   1608                     return true;
   1609                 }
   1610             }
   1611             return false;
   1612         }
   1613 
   1614         private boolean hasWeights() {
   1615             if (!hasWeightsValid) {
   1616                 hasWeights = computeHasWeights();
   1617                 hasWeightsValid = true;
   1618             }
   1619             return hasWeights;
   1620         }
   1621 
   1622         public int[] getDeltas() {
   1623             if (deltas == null) {
   1624                 deltas = new int[getChildCount()];
   1625             }
   1626             return deltas;
   1627         }
   1628 
   1629         private void shareOutDelta(int totalDelta, float totalWeight) {
   1630             Arrays.fill(deltas, 0);
   1631             for (int i = 0, N = getChildCount(); i < N; i++) {
   1632                 final View c = getChildAt(i);
   1633                 if (c.getVisibility() == View.GONE) {
   1634                     continue;
   1635                 }
   1636                 LayoutParams lp = getLayoutParams(c);
   1637                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
   1638                 float weight = spec.weight;
   1639                 if (weight != 0) {
   1640                     int delta = Math.round((weight * totalDelta / totalWeight));
   1641                     deltas[i] = delta;
   1642                     // the two adjustments below are to counter the above rounding and avoid
   1643                     // off-by-ones at the end
   1644                     totalDelta -= delta;
   1645                     totalWeight -= weight;
   1646                 }
   1647             }
   1648         }
   1649 
   1650         private void solveAndDistributeSpace(int[] a) {
   1651             Arrays.fill(getDeltas(), 0);
   1652             solve(a);
   1653             int deltaMax = parentMin.value * getChildCount() + 1; //exclusive
   1654             if (deltaMax < 2) {
   1655                 return; //don't have any delta to distribute
   1656             }
   1657             int deltaMin = 0; //inclusive
   1658 
   1659             float totalWeight = calculateTotalWeight();
   1660 
   1661             int validDelta = -1; //delta for which a solution exists
   1662             boolean validSolution = true;
   1663             // do a binary search to find the max delta that won't conflict with constraints
   1664             while(deltaMin < deltaMax) {
   1665                 // cast to long to prevent overflow.
   1666                 final int delta = (int)(((long)deltaMin + deltaMax) / 2);
   1667                 invalidateValues();
   1668                 shareOutDelta(delta, totalWeight);
   1669                 validSolution = solve(getArcs(), a, false);
   1670                 if (validSolution) {
   1671                     validDelta = delta;
   1672                     deltaMin = delta + 1;
   1673                 } else {
   1674                     deltaMax = delta;
   1675                 }
   1676             }
   1677             if (validDelta > 0 && !validSolution) {
   1678                 // last solution was not successful but we have a successful one. Use it.
   1679                 invalidateValues();
   1680                 shareOutDelta(validDelta, totalWeight);
   1681                 solve(a);
   1682             }
   1683         }
   1684 
   1685         private float calculateTotalWeight() {
   1686             float totalWeight = 0f;
   1687             for (int i = 0, N = getChildCount(); i < N; i++) {
   1688                 View c = getChildAt(i);
   1689                 if (c.getVisibility() == View.GONE) {
   1690                     continue;
   1691                 }
   1692                 LayoutParams lp = getLayoutParams(c);
   1693                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
   1694                 totalWeight += spec.weight;
   1695             }
   1696             return totalWeight;
   1697         }
   1698 
   1699         private void computeLocations(int[] a) {
   1700             if (!hasWeights()) {
   1701                 solve(a);
   1702             } else {
   1703                 solveAndDistributeSpace(a);
   1704             }
   1705             if (!orderPreserved) {
   1706                 // Solve returns the smallest solution to the constraint system for which all
   1707                 // values are positive. One value is therefore zero - though if the row/col
   1708                 // order is not preserved this may not be the first vertex. For consistency,
   1709                 // translate all the values so that they measure the distance from a[0]; the
   1710                 // leading edge of the parent. After this transformation some values may be
   1711                 // negative.
   1712                 int a0 = a[0];
   1713                 for (int i = 0, N = a.length; i < N; i++) {
   1714                     a[i] = a[i] - a0;
   1715                 }
   1716             }
   1717         }
   1718 
   1719         public int[] getLocations() {
   1720             if (locations == null) {
   1721                 int N = getCount() + 1;
   1722                 locations = new int[N];
   1723             }
   1724             if (!locationsValid) {
   1725                 computeLocations(locations);
   1726                 locationsValid = true;
   1727             }
   1728             return locations;
   1729         }
   1730 
   1731         private int size(int[] locations) {
   1732             // The parental edges are attached to vertices 0 and N - even when order is not
   1733             // being preserved and other vertices fall outside this range. Measure the distance
   1734             // between vertices 0 and N, assuming that locations[0] = 0.
   1735             return locations[getCount()];
   1736         }
   1737 
   1738         private void setParentConstraints(int min, int max) {
   1739             parentMin.value = min;
   1740             parentMax.value = -max;
   1741             locationsValid = false;
   1742         }
   1743 
   1744         private int getMeasure(int min, int max) {
   1745             setParentConstraints(min, max);
   1746             return size(getLocations());
   1747         }
   1748 
   1749         public int getMeasure(int measureSpec) {
   1750             int mode = MeasureSpec.getMode(measureSpec);
   1751             int size = MeasureSpec.getSize(measureSpec);
   1752             switch (mode) {
   1753                 case MeasureSpec.UNSPECIFIED: {
   1754                     return getMeasure(0, MAX_SIZE);
   1755                 }
   1756                 case MeasureSpec.EXACTLY: {
   1757                     return getMeasure(size, size);
   1758                 }
   1759                 case MeasureSpec.AT_MOST: {
   1760                     return getMeasure(0, size);
   1761                 }
   1762                 default: {
   1763                     assert false;
   1764                     return 0;
   1765                 }
   1766             }
   1767         }
   1768 
   1769         public void layout(int size) {
   1770             setParentConstraints(size, size);
   1771             getLocations();
   1772         }
   1773 
   1774         public void invalidateStructure() {
   1775             maxIndex = UNDEFINED;
   1776 
   1777             groupBounds = null;
   1778             forwardLinks = null;
   1779             backwardLinks = null;
   1780 
   1781             leadingMargins = null;
   1782             trailingMargins = null;
   1783             arcs = null;
   1784 
   1785             locations = null;
   1786 
   1787             deltas = null;
   1788             hasWeightsValid = false;
   1789 
   1790             invalidateValues();
   1791         }
   1792 
   1793         public void invalidateValues() {
   1794             groupBoundsValid = false;
   1795             forwardLinksValid = false;
   1796             backwardLinksValid = false;
   1797 
   1798             leadingMarginsValid = false;
   1799             trailingMarginsValid = false;
   1800             arcsValid = false;
   1801 
   1802             locationsValid = false;
   1803         }
   1804     }
   1805 
   1806     /**
   1807      * Layout information associated with each of the children of a GridLayout.
   1808      * <p>
   1809      * GridLayout supports both row and column spanning and arbitrary forms of alignment within
   1810      * each cell group. The fundamental parameters associated with each cell group are
   1811      * gathered into their vertical and horizontal components and stored
   1812      * in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
   1813      * {@link GridLayout.Spec Specs} are immutable structures
   1814      * and may be shared between the layout parameters of different children.
   1815      * <p>
   1816      * The row and column specs contain the leading and trailing indices along each axis
   1817      * and together specify the four grid indices that delimit the cells of this cell group.
   1818      * <p>
   1819      * The  alignment properties of the row and column specs together specify
   1820      * both aspects of alignment within the cell group. It is also possible to specify a child's
   1821      * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
   1822      * method.
   1823      * <p>
   1824      * The weight property is also included in Spec and specifies the proportion of any
   1825      * excess space that is due to the associated view.
   1826      *
   1827      * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
   1828      *
   1829      * Because the default values of the {@link #width} and {@link #height}
   1830      * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
   1831      * declared in the layout parameters of GridLayout's children. In addition,
   1832      * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
   1833      * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
   1834      * instead controlled by the principle of <em>flexibility</em>,
   1835      * as discussed in {@link GridLayout}.
   1836      *
   1837      * <h4>Summary</h4>
   1838      *
   1839      * You should not need to use either of the special size values:
   1840      * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
   1841      * a GridLayout.
   1842      *
   1843      * <h4>Default values</h4>
   1844      *
   1845      * <ul>
   1846      *     <li>{@link #width} = {@link #WRAP_CONTENT}</li>
   1847      *     <li>{@link #height} = {@link #WRAP_CONTENT}</li>
   1848      *     <li>{@link #topMargin} = 0 when
   1849      *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
   1850      *          {@code false}; otherwise {@link #UNDEFINED}, to
   1851      *          indicate that a default value should be computed on demand. </li>
   1852      *     <li>{@link #leftMargin} = 0 when
   1853      *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
   1854      *          {@code false}; otherwise {@link #UNDEFINED}, to
   1855      *          indicate that a default value should be computed on demand. </li>
   1856      *     <li>{@link #bottomMargin} = 0 when
   1857      *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
   1858      *          {@code false}; otherwise {@link #UNDEFINED}, to
   1859      *          indicate that a default value should be computed on demand. </li>
   1860      *     <li>{@link #rightMargin} = 0 when
   1861      *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
   1862      *          {@code false}; otherwise {@link #UNDEFINED}, to
   1863      *          indicate that a default value should be computed on demand. </li>
   1864      *     <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
   1865      *     <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
   1866      *     <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
   1867      *     <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
   1868      *     <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
   1869      *     <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
   1870      *     <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
   1871      *     <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
   1872      * </ul>
   1873      *
   1874      * See {@link GridLayout} for a more complete description of the conventions
   1875      * used by GridLayout in the interpretation of the properties of this class.
   1876      *
   1877      * @attr name android:row
   1878      * @attr name android:rowSpan
   1879      * @attr name android:rowWeight
   1880      * @attr name android:column
   1881      * @attr name android:columnSpan
   1882      * @attr name android:columnWeight
   1883      * @attr name android:gravity
   1884      */
   1885     public static class LayoutParams extends MarginLayoutParams {
   1886 
   1887         // Default values
   1888 
   1889         private static final int DEFAULT_WIDTH = WRAP_CONTENT;
   1890         private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
   1891         private static final int DEFAULT_MARGIN = UNDEFINED;
   1892         private static final int DEFAULT_ROW = UNDEFINED;
   1893         private static final int DEFAULT_COLUMN = UNDEFINED;
   1894         private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
   1895         private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
   1896 
   1897         // TypedArray indices
   1898 
   1899         private static final int MARGIN = R.styleable.GridLayout_Layout_android_layout_margin;
   1900         private static final int LEFT_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginLeft;
   1901         private static final int TOP_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginTop;
   1902         private static final int RIGHT_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginRight;
   1903         private static final int BOTTOM_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginBottom;
   1904 
   1905         private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
   1906         private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
   1907         private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
   1908 
   1909         private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
   1910         private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
   1911         private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
   1912 
   1913         private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
   1914 
   1915         // Instance variables
   1916 
   1917         /**
   1918          * The spec that defines the vertical characteristics of the cell group
   1919          * described by these layout parameters.
   1920          * If an assignment is made to this field after a measurement or layout operation
   1921          * has already taken place, a call to
   1922          * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
   1923          * must be made to notify GridLayout of the change. GridLayout is normally able
   1924          * to detect when code fails to observe this rule, issue a warning and take steps to
   1925          * compensate for the omission. This facility is implemented on a best effort basis
   1926          * and should not be relied upon in production code - so it is best to include the above
   1927          * calls to remove the warnings as soon as it is practical.
   1928          */
   1929         public Spec rowSpec = Spec.UNDEFINED;
   1930 
   1931         /**
   1932          * The spec that defines the horizontal characteristics of the cell group
   1933          * described by these layout parameters.
   1934          * If an assignment is made to this field after a measurement or layout operation
   1935          * has already taken place, a call to
   1936          * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
   1937          * must be made to notify GridLayout of the change. GridLayout is normally able
   1938          * to detect when code fails to observe this rule, issue a warning and take steps to
   1939          * compensate for the omission. This facility is implemented on a best effort basis
   1940          * and should not be relied upon in production code - so it is best to include the above
   1941          * calls to remove the warnings as soon as it is practical.
   1942          */
   1943         public Spec columnSpec = Spec.UNDEFINED;
   1944 
   1945         // Constructors
   1946 
   1947         private LayoutParams(
   1948                 int width, int height,
   1949                 int left, int top, int right, int bottom,
   1950                 Spec rowSpec, Spec columnSpec) {
   1951             super(width, height);
   1952             setMargins(left, top, right, bottom);
   1953             this.rowSpec = rowSpec;
   1954             this.columnSpec = columnSpec;
   1955         }
   1956 
   1957         /**
   1958          * Constructs a new LayoutParams instance for this <code>rowSpec</code>
   1959          * and <code>columnSpec</code>. All other fields are initialized with
   1960          * default values as defined in {@link LayoutParams}.
   1961          *
   1962          * @param rowSpec    the rowSpec
   1963          * @param columnSpec the columnSpec
   1964          */
   1965         public LayoutParams(Spec rowSpec, Spec columnSpec) {
   1966             this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
   1967                     DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
   1968                     rowSpec, columnSpec);
   1969         }
   1970 
   1971         /**
   1972          * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
   1973          */
   1974         public LayoutParams() {
   1975             this(Spec.UNDEFINED, Spec.UNDEFINED);
   1976         }
   1977 
   1978         // Copying constructors
   1979 
   1980         /**
   1981          * {@inheritDoc}
   1982          */
   1983         public LayoutParams(ViewGroup.LayoutParams params) {
   1984             super(params);
   1985         }
   1986 
   1987         /**
   1988          * {@inheritDoc}
   1989          */
   1990         public LayoutParams(MarginLayoutParams params) {
   1991             super(params);
   1992         }
   1993 
   1994         /**
   1995          * Copy constructor. Clones the width, height, margin values, row spec,
   1996          * and column spec of the source.
   1997          *
   1998          * @param source The layout params to copy from.
   1999          */
   2000         public LayoutParams(LayoutParams source) {
   2001             super(source);
   2002 
   2003             this.rowSpec = source.rowSpec;
   2004             this.columnSpec = source.columnSpec;
   2005         }
   2006 
   2007         // AttributeSet constructors
   2008 
   2009         /**
   2010          * {@inheritDoc}
   2011          *
   2012          * Values not defined in the attribute set take the default values
   2013          * defined in {@link LayoutParams}.
   2014          */
   2015         public LayoutParams(Context context, AttributeSet attrs) {
   2016             super(context, attrs);
   2017             reInitSuper(context, attrs);
   2018             init(context, attrs);
   2019         }
   2020 
   2021         // Implementation
   2022 
   2023         // Reinitialise the margins using a different default policy than MarginLayoutParams.
   2024         // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
   2025         // so that a layout manager default can be accessed post set up. We need this as, at the
   2026         // point of installation, we do not know how many rows/cols there are and therefore
   2027         // which elements are positioned next to the container's trailing edges. We need to
   2028         // know this as margins around the container's boundary should have different
   2029         // defaults to those between peers.
   2030 
   2031         // This method could be parametrized and moved into MarginLayout.
   2032         private void reInitSuper(Context context, AttributeSet attrs) {
   2033             TypedArray a =
   2034                     context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
   2035             try {
   2036                 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
   2037 
   2038                 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
   2039                 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
   2040                 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
   2041                 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
   2042             } finally {
   2043                 a.recycle();
   2044             }
   2045         }
   2046 
   2047         private void init(Context context, AttributeSet attrs) {
   2048             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
   2049             try {
   2050                 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
   2051 
   2052                 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
   2053                 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
   2054                 float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
   2055                 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
   2056 
   2057                 int row = a.getInt(ROW, DEFAULT_ROW);
   2058                 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
   2059                 float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
   2060                 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
   2061             } finally {
   2062                 a.recycle();
   2063             }
   2064         }
   2065 
   2066         /**
   2067          * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
   2068          * See {@link Gravity}.
   2069          *
   2070          * @param gravity the new gravity value
   2071          *
   2072          * @attr name android:gravity
   2073          */
   2074         public void setGravity(int gravity) {
   2075             rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
   2076             columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
   2077         }
   2078 
   2079         @Override
   2080         protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
   2081             this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
   2082             this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
   2083         }
   2084 
   2085         final void setRowSpecSpan(Interval span) {
   2086             rowSpec = rowSpec.copyWriteSpan(span);
   2087         }
   2088 
   2089         final void setColumnSpecSpan(Interval span) {
   2090             columnSpec = columnSpec.copyWriteSpan(span);
   2091         }
   2092 
   2093         @Override
   2094         public boolean equals(Object o) {
   2095             if (this == o) return true;
   2096             if (o == null || getClass() != o.getClass()) return false;
   2097 
   2098             LayoutParams that = (LayoutParams) o;
   2099 
   2100             if (!columnSpec.equals(that.columnSpec)) return false;
   2101             if (!rowSpec.equals(that.rowSpec)) return false;
   2102 
   2103             return true;
   2104         }
   2105 
   2106         @Override
   2107         public int hashCode() {
   2108             int result = rowSpec.hashCode();
   2109             result = 31 * result + columnSpec.hashCode();
   2110             return result;
   2111         }
   2112     }
   2113 
   2114     /*
   2115     In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
   2116     Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
   2117      */
   2118     final static class Arc {
   2119         public final Interval span;
   2120         public final MutableInt value;
   2121         public boolean valid = true;
   2122 
   2123         public Arc(Interval span, MutableInt value) {
   2124             this.span = span;
   2125             this.value = value;
   2126         }
   2127 
   2128         @Override
   2129         public String toString() {
   2130             return span + " " + (!valid ? "+>" : "->") + " " + value;
   2131         }
   2132     }
   2133 
   2134     // A mutable Integer - used to avoid heap allocation during the layout operation
   2135 
   2136     final static class MutableInt {
   2137         public int value;
   2138 
   2139         public MutableInt() {
   2140             reset();
   2141         }
   2142 
   2143         public MutableInt(int value) {
   2144             this.value = value;
   2145         }
   2146 
   2147         public void reset() {
   2148             value = Integer.MIN_VALUE;
   2149         }
   2150 
   2151         @Override
   2152         public String toString() {
   2153             return Integer.toString(value);
   2154         }
   2155     }
   2156 
   2157     final static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
   2158         private final Class<K> keyType;
   2159         private final Class<V> valueType;
   2160 
   2161         private Assoc(Class<K> keyType, Class<V> valueType) {
   2162             this.keyType = keyType;
   2163             this.valueType = valueType;
   2164         }
   2165 
   2166         public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
   2167             return new Assoc<K, V>(keyType, valueType);
   2168         }
   2169 
   2170         public void put(K key, V value) {
   2171             add(Pair.create(key, value));
   2172         }
   2173 
   2174         @SuppressWarnings(value = "unchecked")
   2175         public PackedMap<K, V> pack() {
   2176             int N = size();
   2177             K[] keys = (K[]) Array.newInstance(keyType, N);
   2178             V[] values = (V[]) Array.newInstance(valueType, N);
   2179             for (int i = 0; i < N; i++) {
   2180                 keys[i] = get(i).first;
   2181                 values[i] = get(i).second;
   2182             }
   2183             return new PackedMap<K, V>(keys, values);
   2184         }
   2185     }
   2186 
   2187     /*
   2188     This data structure is used in place of a Map where we have an index that refers to the order
   2189     in which each key/value pairs were added to the map. In this case we store keys and values
   2190     in arrays of a length that is equal to the number of unique keys. We also maintain an
   2191     array of indexes from insertion order to the compacted arrays of keys and values.
   2192 
   2193     Note that behavior differs from that of a LinkedHashMap in that repeated entries
   2194     *do* get added multiples times. So the length of index is equals to the number of
   2195     items added.
   2196 
   2197     This is useful in the GridLayout class where we can rely on the order of children not
   2198     changing during layout - to use integer-based lookup for our internal structures
   2199     rather than using (and storing) an implementation of Map<Key, ?>.
   2200      */
   2201     @SuppressWarnings(value = "unchecked")
   2202     final static class PackedMap<K, V> {
   2203         public final int[] index;
   2204         public final K[] keys;
   2205         public final V[] values;
   2206 
   2207         PackedMap(K[] keys, V[] values) {
   2208             this.index = createIndex(keys);
   2209 
   2210             this.keys = compact(keys, index);
   2211             this.values = compact(values, index);
   2212         }
   2213 
   2214         public V getValue(int i) {
   2215             return values[index[i]];
   2216         }
   2217 
   2218         private static <K> int[] createIndex(K[] keys) {
   2219             int size = keys.length;
   2220             int[] result = new int[size];
   2221 
   2222             Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
   2223             for (int i = 0; i < size; i++) {
   2224                 K key = keys[i];
   2225                 Integer index = keyToIndex.get(key);
   2226                 if (index == null) {
   2227                     index = keyToIndex.size();
   2228                     keyToIndex.put(key, index);
   2229                 }
   2230                 result[i] = index;
   2231             }
   2232             return result;
   2233         }
   2234 
   2235         /*
   2236         Create a compact array of keys or values using the supplied index.
   2237          */
   2238         private static <K> K[] compact(K[] a, int[] index) {
   2239             int size = a.length;
   2240             Class<?> componentType = a.getClass().getComponentType();
   2241             K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1);
   2242 
   2243             // this overwrite duplicates, retaining the last equivalent entry
   2244             for (int i = 0; i < size; i++) {
   2245                 result[index[i]] = a[i];
   2246             }
   2247             return result;
   2248         }
   2249     }
   2250 
   2251     /*
   2252     For each group (with a given alignment) we need to store the amount of space required
   2253     before the alignment point and the amount of space required after it. One side of this
   2254     calculation is always 0 for START and END alignments but we don't make use of this.
   2255     For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
   2256     simple optimisations are possible.
   2257 
   2258     The general algorithm therefore is to create a Map (actually a PackedMap) from
   2259     group to Bounds and to loop through all Views in the group taking the maximum
   2260     of the values for each View.
   2261     */
   2262     static class Bounds {
   2263         public int before;
   2264         public int after;
   2265         public int flexibility; // we're flexible iff all included specs are flexible
   2266 
   2267         Bounds() {
   2268             reset();
   2269         }
   2270 
   2271         protected void reset() {
   2272             before = Integer.MIN_VALUE;
   2273             after = Integer.MIN_VALUE;
   2274             flexibility = CAN_STRETCH; // from the above, we're flexible when empty
   2275         }
   2276 
   2277         protected void include(int before, int after) {
   2278             this.before = max(this.before, before);
   2279             this.after = max(this.after, after);
   2280         }
   2281 
   2282         protected int size(boolean min) {
   2283             if (!min) {
   2284                 if (canStretch(flexibility)) {
   2285                     return MAX_SIZE;
   2286                 }
   2287             }
   2288             return before + after;
   2289         }
   2290 
   2291         protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
   2292             return before - a.getAlignmentValue(c, size, ViewGroupCompat.getLayoutMode(gl));
   2293         }
   2294 
   2295         protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
   2296             this.flexibility &= spec.getFlexibility();
   2297             boolean horizontal = axis.horizontal;
   2298             Alignment alignment = spec.getAbsoluteAlignment(horizontal);
   2299             // todo test this works correctly when the returned value is UNDEFINED
   2300             int before = alignment.getAlignmentValue(c, size, ViewGroupCompat.getLayoutMode(gl));
   2301             include(before, size - before);
   2302         }
   2303 
   2304         @Override
   2305         public String toString() {
   2306             return "Bounds{" +
   2307                     "before=" + before +
   2308                     ", after=" + after +
   2309                     '}';
   2310         }
   2311     }
   2312 
   2313     /**
   2314      * An Interval represents a contiguous range of values that lie between
   2315      * the interval's {@link #min} and {@link #max} values.
   2316      * <p>
   2317      * Intervals are immutable so may be passed as values and used as keys in hash tables.
   2318      * It is not necessary to have multiple instances of Intervals which have the same
   2319      * {@link #min} and {@link #max} values.
   2320      * <p>
   2321      * Intervals are often written as {@code [min, max]} and represent the set of values
   2322      * {@code x} such that {@code min <= x < max}.
   2323      */
   2324     final static class Interval {
   2325         /**
   2326          * The minimum value.
   2327          */
   2328         public final int min;
   2329 
   2330         /**
   2331          * The maximum value.
   2332          */
   2333         public final int max;
   2334 
   2335         /**
   2336          * Construct a new Interval, {@code interval}, where:
   2337          * <ul>
   2338          *     <li> {@code interval.min = min} </li>
   2339          *     <li> {@code interval.max = max} </li>
   2340          * </ul>
   2341          *
   2342          * @param min the minimum value.
   2343          * @param max the maximum value.
   2344          */
   2345         public Interval(int min, int max) {
   2346             this.min = min;
   2347             this.max = max;
   2348         }
   2349 
   2350         int size() {
   2351             return max - min;
   2352         }
   2353 
   2354         Interval inverse() {
   2355             return new Interval(max, min);
   2356         }
   2357 
   2358         /**
   2359          * Returns {@code true} if the {@link #getClass class},
   2360          * {@link #min} and {@link #max} properties of this Interval and the
   2361          * supplied parameter are pairwise equal; {@code false} otherwise.
   2362          *
   2363          * @param that the object to compare this interval with
   2364          *
   2365          * @return {@code true} if the specified object is equal to this
   2366          *         {@code Interval}, {@code false} otherwise.
   2367          */
   2368         @Override
   2369         public boolean equals(Object that) {
   2370             if (this == that) {
   2371                 return true;
   2372             }
   2373             if (that == null || getClass() != that.getClass()) {
   2374                 return false;
   2375             }
   2376 
   2377             Interval interval = (Interval) that;
   2378 
   2379             if (max != interval.max) {
   2380                 return false;
   2381             }
   2382             //noinspection RedundantIfStatement
   2383             if (min != interval.min) {
   2384                 return false;
   2385             }
   2386 
   2387             return true;
   2388         }
   2389 
   2390         @Override
   2391         public int hashCode() {
   2392             int result = min;
   2393             result = 31 * result + max;
   2394             return result;
   2395         }
   2396 
   2397         @Override
   2398         public String toString() {
   2399             return "[" + min + ", " + max + "]";
   2400         }
   2401     }
   2402 
   2403     /**
   2404      * A Spec defines the horizontal or vertical characteristics of a group of
   2405      * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em>
   2406      * along the appropriate axis.
   2407      * <p>
   2408      * The <em>grid indices</em> are the leading and trailing edges of this cell group.
   2409      * See {@link GridLayout} for a description of the conventions used by GridLayout
   2410      * for grid indices.
   2411      * <p>
   2412      * The <em>alignment</em> property specifies how cells should be aligned in this group.
   2413      * For row groups, this specifies the vertical alignment.
   2414      * For column groups, this specifies the horizontal alignment.
   2415      * <p>
   2416      * Use the following static methods to create specs:
   2417      * <ul>
   2418      *   <li>{@link #spec(int)}</li>
   2419      *   <li>{@link #spec(int, int)}</li>
   2420      *   <li>{@link #spec(int, Alignment)}</li>
   2421      *   <li>{@link #spec(int, int, Alignment)}</li>
   2422      *   <li>{@link #spec(int, float)}</li>
   2423      *   <li>{@link #spec(int, int, float)}</li>
   2424      *   <li>{@link #spec(int, Alignment, float)}</li>
   2425      *   <li>{@link #spec(int, int, Alignment, float)}</li>
   2426      * </ul>
   2427      *
   2428      */
   2429     public static class Spec {
   2430         static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
   2431         static final float DEFAULT_WEIGHT = 0;
   2432 
   2433         final boolean startDefined;
   2434         final Interval span;
   2435         final Alignment alignment;
   2436         final float weight;
   2437 
   2438         private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
   2439             this.startDefined = startDefined;
   2440             this.span = span;
   2441             this.alignment = alignment;
   2442             this.weight = weight;
   2443         }
   2444 
   2445         Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
   2446             this(startDefined, new Interval(start, start + size), alignment, weight);
   2447         }
   2448 
   2449         public Alignment getAbsoluteAlignment(boolean horizontal) {
   2450             if (alignment != UNDEFINED_ALIGNMENT) {
   2451                 return alignment;
   2452             }
   2453             if (weight == 0f) {
   2454                 return horizontal ? START : BASELINE;
   2455             }
   2456             return FILL;
   2457         }
   2458 
   2459         final Spec copyWriteSpan(Interval span) {
   2460             return new Spec(startDefined, span, alignment, weight);
   2461         }
   2462 
   2463         final Spec copyWriteAlignment(Alignment alignment) {
   2464             return new Spec(startDefined, span, alignment, weight);
   2465         }
   2466 
   2467         final int getFlexibility() {
   2468             return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
   2469         }
   2470 
   2471         /**
   2472          * Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
   2473          * properties of this Spec and the supplied parameter are pairwise equal,
   2474          * {@code false} otherwise.
   2475          *
   2476          * @param that the object to compare this spec with
   2477          *
   2478          * @return {@code true} if the specified object is equal to this
   2479          *         {@code Spec}; {@code false} otherwise
   2480          */
   2481         @Override
   2482         public boolean equals(Object that) {
   2483             if (this == that) {
   2484                 return true;
   2485             }
   2486             if (that == null || getClass() != that.getClass()) {
   2487                 return false;
   2488             }
   2489 
   2490             Spec spec = (Spec) that;
   2491 
   2492             if (!alignment.equals(spec.alignment)) {
   2493                 return false;
   2494             }
   2495             //noinspection RedundantIfStatement
   2496             if (!span.equals(spec.span)) {
   2497                 return false;
   2498             }
   2499 
   2500             return true;
   2501         }
   2502 
   2503         @Override
   2504         public int hashCode() {
   2505             int result = span.hashCode();
   2506             result = 31 * result + alignment.hashCode();
   2507             return result;
   2508         }
   2509     }
   2510 
   2511     /**
   2512      * Return a Spec, {@code spec}, where:
   2513      * <ul>
   2514      *     <li> {@code spec.span = [start, start + size]} </li>
   2515      *     <li> {@code spec.alignment = alignment} </li>
   2516      *     <li> {@code spec.weight = weight} </li>
   2517      * </ul>
   2518      * <p>
   2519      * To leave the start index undefined, use the value {@link #UNDEFINED}.
   2520      *
   2521      * @param start     the start
   2522      * @param size      the size
   2523      * @param alignment the alignment
   2524      * @param weight    the weight
   2525      */
   2526     public static Spec spec(int start, int size, Alignment alignment, float weight) {
   2527         return new Spec(start != UNDEFINED, start, size, alignment, weight);
   2528     }
   2529 
   2530     /**
   2531      * Equivalent to: {@code spec(start, 1, alignment, weight)}.
   2532      *
   2533      * @param start     the start
   2534      * @param alignment the alignment
   2535      * @param weight    the weight
   2536      */
   2537     public static Spec spec(int start, Alignment alignment, float weight) {
   2538         return spec(start, 1, alignment, weight);
   2539     }
   2540 
   2541     /**
   2542      * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
   2543      * where {@code default_alignment} is specified in
   2544      * {@link android.widget.GridLayout.LayoutParams}.
   2545      *
   2546      * @param start  the start
   2547      * @param size   the size
   2548      * @param weight the weight
   2549      */
   2550     public static Spec spec(int start, int size, float weight) {
   2551         return spec(start, size, UNDEFINED_ALIGNMENT, weight);
   2552     }
   2553 
   2554     /**
   2555      * Equivalent to: {@code spec(start, 1, weight)}.
   2556      *
   2557      * @param start  the start
   2558      * @param weight the weight
   2559      */
   2560     public static Spec spec(int start, float weight) {
   2561         return spec(start, 1, weight);
   2562     }
   2563 
   2564     /**
   2565      * Equivalent to: {@code spec(start, size, alignment, 0f)}.
   2566      *
   2567      * @param start     the start
   2568      * @param size      the size
   2569      * @param alignment the alignment
   2570      */
   2571     public static Spec spec(int start, int size, Alignment alignment) {
   2572         return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
   2573     }
   2574 
   2575     /**
   2576      * Return a Spec, {@code spec}, where:
   2577      * <ul>
   2578      *     <li> {@code spec.span = [start, start + 1]} </li>
   2579      *     <li> {@code spec.alignment = alignment} </li>
   2580      * </ul>
   2581      * <p>
   2582      * To leave the start index undefined, use the value {@link #UNDEFINED}.
   2583      *
   2584      * @param start     the start index
   2585      * @param alignment the alignment
   2586      *
   2587      * @see #spec(int, int, Alignment)
   2588      */
   2589     public static Spec spec(int start, Alignment alignment) {
   2590         return spec(start, 1, alignment);
   2591     }
   2592 
   2593     /**
   2594      * Return a Spec, {@code spec}, where:
   2595      * <ul>
   2596      *     <li> {@code spec.span = [start, start + size]} </li>
   2597      * </ul>
   2598      * <p>
   2599      * To leave the start index undefined, use the value {@link #UNDEFINED}.
   2600      *
   2601      * @param start     the start
   2602      * @param size      the size
   2603      *
   2604      * @see #spec(int, Alignment)
   2605      */
   2606     public static Spec spec(int start, int size) {
   2607         return spec(start, size, UNDEFINED_ALIGNMENT);
   2608     }
   2609 
   2610     /**
   2611      * Return a Spec, {@code spec}, where:
   2612      * <ul>
   2613      *     <li> {@code spec.span = [start, start + 1]} </li>
   2614      * </ul>
   2615      * <p>
   2616      * To leave the start index undefined, use the value {@link #UNDEFINED}.
   2617      *
   2618      * @param start     the start index
   2619      *
   2620      * @see #spec(int, int)
   2621      */
   2622     public static Spec spec(int start) {
   2623         return spec(start, 1);
   2624     }
   2625 
   2626     /**
   2627      * Alignments specify where a view should be placed within a cell group and
   2628      * what size it should be.
   2629      * <p>
   2630      * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
   2631      * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
   2632      * {@code alignment}. Overall placement of the view in the cell
   2633      * group is specified by the two alignments which act along each axis independently.
   2634      * <p>
   2635      *  The GridLayout class defines the most common alignments used in general layout:
   2636      * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
   2637      * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
   2638      */
   2639     /*
   2640      * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
   2641      * to return the appropriate value for the type of alignment being defined.
   2642      * The enclosing algorithms position the children
   2643      * so that the locations defined by the alignment values
   2644      * are the same for all of the views in a group.
   2645      * <p>
   2646      */
   2647     public static abstract class Alignment {
   2648         Alignment() {
   2649         }
   2650 
   2651         abstract int getGravityOffset(View view, int cellDelta);
   2652 
   2653         /**
   2654          * Returns an alignment value. In the case of vertical alignments the value
   2655          * returned should indicate the distance from the top of the view to the
   2656          * alignment location.
   2657          * For horizontal alignments measurement is made from the left edge of the component.
   2658          *
   2659          * @param view              the view to which this alignment should be applied
   2660          * @param viewSize          the measured size of the view
   2661          * @param mode              the basis of alignment: CLIP or OPTICAL
   2662          * @return the alignment value
   2663          */
   2664         abstract int getAlignmentValue(View view, int viewSize, int mode);
   2665 
   2666         /**
   2667          * Returns the size of the view specified by this alignment.
   2668          * In the case of vertical alignments this method should return a height; for
   2669          * horizontal alignments this method should return the width.
   2670          * <p>
   2671          * The default implementation returns {@code viewSize}.
   2672          *
   2673          * @param view              the view to which this alignment should be applied
   2674          * @param viewSize          the measured size of the view
   2675          * @param cellSize          the size of the cell into which this view will be placed
   2676          * @return the aligned size
   2677          */
   2678         int getSizeInCell(View view, int viewSize, int cellSize) {
   2679             return viewSize;
   2680         }
   2681 
   2682         Bounds getBounds() {
   2683             return new Bounds();
   2684         }
   2685 
   2686         abstract String getDebugString();
   2687 
   2688         @Override
   2689         public String toString() {
   2690             return "Alignment:" + getDebugString();
   2691         }
   2692     }
   2693 
   2694     static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
   2695         @Override
   2696         int getGravityOffset(View view, int cellDelta) {
   2697             return UNDEFINED;
   2698         }
   2699 
   2700         @Override
   2701         public int getAlignmentValue(View view, int viewSize, int mode) {
   2702             return UNDEFINED;
   2703         }
   2704 
   2705         @Override
   2706         String getDebugString() {
   2707             return "UNDEFINED";
   2708         }
   2709     };
   2710 
   2711     /**
   2712      * Indicates that a view should be aligned with the <em>start</em>
   2713      * edges of the other views in its cell group.
   2714      */
   2715     private static final Alignment LEADING = new Alignment() {
   2716         @Override
   2717         int getGravityOffset(View view, int cellDelta) {
   2718             return 0;
   2719         }
   2720 
   2721         @Override
   2722         public int getAlignmentValue(View view, int viewSize, int mode) {
   2723             return 0;
   2724         }
   2725 
   2726         @Override
   2727         String getDebugString() {
   2728             return "LEADING";
   2729         }
   2730     };
   2731 
   2732     /**
   2733      * Indicates that a view should be aligned with the <em>end</em>
   2734      * edges of the other views in its cell group.
   2735      */
   2736     private static final Alignment TRAILING = new Alignment() {
   2737         @Override
   2738         int getGravityOffset(View view, int cellDelta) {
   2739             return cellDelta;
   2740         }
   2741 
   2742         @Override
   2743         public int getAlignmentValue(View view, int viewSize, int mode) {
   2744             return viewSize;
   2745         }
   2746 
   2747         @Override
   2748         String getDebugString() {
   2749             return "TRAILING";
   2750         }
   2751     };
   2752 
   2753     /**
   2754      * Indicates that a view should be aligned with the <em>top</em>
   2755      * edges of the other views in its cell group.
   2756      */
   2757     public static final Alignment TOP = LEADING;
   2758 
   2759     /**
   2760      * Indicates that a view should be aligned with the <em>bottom</em>
   2761      * edges of the other views in its cell group.
   2762      */
   2763     public static final Alignment BOTTOM = TRAILING;
   2764 
   2765     /**
   2766      * Indicates that a view should be aligned with the <em>start</em>
   2767      * edges of the other views in its cell group.
   2768      */
   2769     public static final Alignment START = LEADING;
   2770 
   2771     /**
   2772      * Indicates that a view should be aligned with the <em>end</em>
   2773      * edges of the other views in its cell group.
   2774      */
   2775     public static final Alignment END = TRAILING;
   2776 
   2777     private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
   2778         return new Alignment() {
   2779             @Override
   2780             int getGravityOffset(View view, int cellDelta) {
   2781                 boolean isLayoutRtl = ViewCompat.getLayoutDirection(view) ==
   2782                         ViewCompat.LAYOUT_DIRECTION_RTL;
   2783                 return (!isLayoutRtl ? ltr : rtl).getGravityOffset(view, cellDelta);
   2784             }
   2785 
   2786             @Override
   2787             public int getAlignmentValue(View view, int viewSize, int mode) {
   2788                 boolean isLayoutRtl = ViewCompat.getLayoutDirection(view) ==
   2789                         ViewCompat.LAYOUT_DIRECTION_RTL;
   2790                 return (!isLayoutRtl ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
   2791             }
   2792 
   2793             @Override
   2794             String getDebugString() {
   2795                 return "SWITCHING[L:" + ltr.getDebugString() + ", R:" + rtl.getDebugString() + "]";
   2796             }
   2797         };
   2798     }
   2799 
   2800     /**
   2801      * Indicates that a view should be aligned with the <em>left</em>
   2802      * edges of the other views in its cell group.
   2803      */
   2804     public static final Alignment LEFT = createSwitchingAlignment(START, END);
   2805 
   2806     /**
   2807      * Indicates that a view should be aligned with the <em>right</em>
   2808      * edges of the other views in its cell group.
   2809      */
   2810     public static final Alignment RIGHT = createSwitchingAlignment(END, START);
   2811 
   2812     /**
   2813      * Indicates that a view should be <em>centered</em> with the other views in its cell group.
   2814      * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
   2815      * LayoutParams#columnSpec columnSpecs}.
   2816      */
   2817     public static final Alignment CENTER = new Alignment() {
   2818         @Override
   2819         int getGravityOffset(View view, int cellDelta) {
   2820             return cellDelta >> 1;
   2821         }
   2822 
   2823         @Override
   2824         public int getAlignmentValue(View view, int viewSize, int mode) {
   2825             return viewSize >> 1;
   2826         }
   2827 
   2828         @Override
   2829         String getDebugString() {
   2830             return "CENTER";
   2831         }
   2832     };
   2833 
   2834     /**
   2835      * Indicates that a view should be aligned with the <em>baselines</em>
   2836      * of the other views in its cell group.
   2837      * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
   2838      *
   2839      * @see View#getBaseline()
   2840      */
   2841     public static final Alignment BASELINE = new Alignment() {
   2842         @Override
   2843         int getGravityOffset(View view, int cellDelta) {
   2844             return 0; // baseline gravity is top
   2845         }
   2846 
   2847         @Override
   2848         public int getAlignmentValue(View view, int viewSize, int mode) {
   2849             if (view.getVisibility() == GONE) {
   2850                 return 0;
   2851             }
   2852             int baseline = view.getBaseline();
   2853             return baseline == -1 ? UNDEFINED : baseline;
   2854         }
   2855 
   2856         @Override
   2857         public Bounds getBounds() {
   2858             return new Bounds() {
   2859                 /*
   2860                 In a baseline aligned row in which some components define a baseline
   2861                 and some don't, we need a third variable to properly account for all
   2862                 the sizes. This tracks the maximum size of all the components -
   2863                 including those that don't define a baseline.
   2864                 */
   2865                 private int size;
   2866 
   2867                 @Override
   2868                 protected void reset() {
   2869                     super.reset();
   2870                     size = Integer.MIN_VALUE;
   2871                 }
   2872 
   2873                 @Override
   2874                 protected void include(int before, int after) {
   2875                     super.include(before, after);
   2876                     size = max(size, before + after);
   2877                 }
   2878 
   2879                 @Override
   2880                 protected int size(boolean min) {
   2881                     return max(super.size(min), size);
   2882                 }
   2883 
   2884                 @Override
   2885                 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
   2886                     return max(0, super.getOffset(gl, c, a, size, hrz));
   2887                 }
   2888             };
   2889         }
   2890 
   2891         @Override
   2892         String getDebugString() {
   2893             return "BASELINE";
   2894         }
   2895     };
   2896 
   2897     /**
   2898      * Indicates that a view should expanded to fit the boundaries of its cell group.
   2899      * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
   2900      * {@link LayoutParams#columnSpec columnSpecs}.
   2901      */
   2902     public static final Alignment FILL = new Alignment() {
   2903         @Override
   2904         int getGravityOffset(View view, int cellDelta) {
   2905             return 0;
   2906         }
   2907 
   2908         @Override
   2909         public int getAlignmentValue(View view, int viewSize, int mode) {
   2910             return UNDEFINED;
   2911         }
   2912 
   2913         @Override
   2914         public int getSizeInCell(View view, int viewSize, int cellSize) {
   2915             return cellSize;
   2916         }
   2917 
   2918         @Override
   2919         String getDebugString() {
   2920             return "FILL";
   2921         }
   2922     };
   2923 
   2924     static boolean canStretch(int flexibility) {
   2925         return (flexibility & CAN_STRETCH) != 0;
   2926     }
   2927 
   2928     static final int INFLEXIBLE = 0;
   2929     static final int CAN_STRETCH = 2;
   2930 }
   2931