blob: f1321979e2ded2ab5a0aed1d487ec5393413fab9 [file] [log] [blame]
Philip Milne3f8956d2011-05-13 17:29:00 +01001/*
2 * Copyright (C) 2011 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
17package android.widget;
18
Aurimas Liutikas99441c52016-10-11 16:48:32 -070019import static android.view.Gravity.AXIS_PULL_AFTER;
20import static android.view.Gravity.AXIS_PULL_BEFORE;
21import static android.view.Gravity.AXIS_SPECIFIED;
22import static android.view.Gravity.AXIS_X_SHIFT;
23import static android.view.Gravity.AXIS_Y_SHIFT;
24import static android.view.Gravity.HORIZONTAL_GRAVITY_MASK;
25import static android.view.Gravity.RELATIVE_LAYOUT_DIRECTION;
26import static android.view.Gravity.VERTICAL_GRAVITY_MASK;
27import static android.view.View.MeasureSpec.EXACTLY;
28import static android.view.View.MeasureSpec.makeMeasureSpec;
29
30import static java.lang.Math.max;
31import static java.lang.Math.min;
32
Tor Norbyed9273d62013-05-30 15:59:53 -070033import android.annotation.IntDef;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000034import android.compat.annotation.UnsupportedAppUsage;
Philip Milne3f8956d2011-05-13 17:29:00 +010035import android.content.Context;
36import android.content.res.TypedArray;
37import android.graphics.Canvas;
38import android.graphics.Color;
Philip Milne1557fd72012-04-04 23:41:34 -070039import android.graphics.Insets;
Philip Milne3f8956d2011-05-13 17:29:00 +010040import android.graphics.Paint;
Philip Milne3f8956d2011-05-13 17:29:00 +010041import android.util.AttributeSet;
42import android.util.Log;
Philip Milne211d0332012-04-24 14:45:14 -070043import android.util.LogPrinter;
Philip Milne48b55242011-06-29 11:09:45 -070044import android.util.Pair;
Philip Milne211d0332012-04-24 14:45:14 -070045import android.util.Printer;
Philip Milne3f8956d2011-05-13 17:29:00 +010046import android.view.Gravity;
47import android.view.View;
48import android.view.ViewGroup;
Ashley Rose55f9f922019-01-28 19:29:36 -050049import android.view.inspector.InspectableProperty;
Philip Milneedd69512012-03-14 17:21:33 -070050import android.widget.RemoteViews.RemoteView;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070051
Philip Milne10ca24a2012-04-23 15:38:27 -070052import com.android.internal.R;
Philip Milne3f8956d2011-05-13 17:29:00 +010053
Tor Norbyed9273d62013-05-30 15:59:53 -070054import java.lang.annotation.Retention;
55import java.lang.annotation.RetentionPolicy;
Philip Milne3f8956d2011-05-13 17:29:00 +010056import java.lang.reflect.Array;
57import java.util.ArrayList;
58import java.util.Arrays;
Philip Milne3f8956d2011-05-13 17:29:00 +010059import java.util.HashMap;
60import java.util.List;
61import java.util.Map;
62
Philip Milne3f8956d2011-05-13 17:29:00 +010063/**
64 * A layout that places its children in a rectangular <em>grid</em>.
65 * <p>
66 * The grid is composed of a set of infinitely thin lines that separate the
67 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
Philip Milne7fd94872011-06-07 20:14:17 -070068 * by grid <em>indices</em>. A grid with {@code N} columns
69 * has {@code N + 1} grid indices that run from {@code 0}
70 * through {@code N} inclusive. Regardless of how GridLayout is
71 * configured, grid index {@code 0} is fixed to the leading edge of the
72 * container and grid index {@code N} is fixed to its trailing edge
Philip Milne3f8956d2011-05-13 17:29:00 +010073 * (after padding is taken into account).
74 *
Philip Milne93cd6a62011-07-12 14:49:45 -070075 * <h4>Row and Column Specs</h4>
Philip Milne3f8956d2011-05-13 17:29:00 +010076 *
77 * Children occupy one or more contiguous cells, as defined
Philip Milne93cd6a62011-07-12 14:49:45 -070078 * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and
79 * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters.
80 * Each spec defines the set of rows or columns that are to be
Philip Milne3f8956d2011-05-13 17:29:00 +010081 * occupied; and how children should be aligned within the resulting group of cells.
82 * Although cells do not normally overlap in a GridLayout, GridLayout does
83 * not prevent children being defined to occupy the same cell or group of cells.
84 * In this case however, there is no guarantee that children will not themselves
85 * overlap after the layout operation completes.
86 *
87 * <h4>Default Cell Assignment</h4>
88 *
Philip Milne48b55242011-06-29 11:09:45 -070089 * If a child does not specify the row and column indices of the cell it
Philip Milne3f8956d2011-05-13 17:29:00 +010090 * wishes to occupy, GridLayout assigns cell locations automatically using its:
91 * {@link GridLayout#setOrientation(int) orientation},
92 * {@link GridLayout#setRowCount(int) rowCount} and
93 * {@link GridLayout#setColumnCount(int) columnCount} properties.
94 *
95 * <h4>Space</h4>
96 *
97 * Space between children may be specified either by using instances of the
98 * dedicated {@link Space} view or by setting the
99 *
100 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
101 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
102 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
103 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
104 *
105 * layout parameters. When the
106 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
107 * property is set, default margins around children are automatically
Philip Milnef6679c82011-09-18 11:36:57 -0700108 * allocated based on the prevailing UI style guide for the platform.
109 * Each of the margins so defined may be independently overridden by an assignment
Philip Milne3f8956d2011-05-13 17:29:00 +0100110 * to the appropriate layout parameter.
Philip Milnef6679c82011-09-18 11:36:57 -0700111 * Default values will generally produce a reasonable spacing between components
112 * but values may change between different releases of the platform.
Philip Milne3f8956d2011-05-13 17:29:00 +0100113 *
114 * <h4>Excess Space Distribution</h4>
115 *
Philip Milne87260842014-05-22 13:56:03 -0700116 * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight.
117 * In the event that no weights are specified, the previous conventions are respected and
118 * columns and rows are taken as flexible if their views specify some form of alignment
119 * within their groups.
Philip Milnef6679c82011-09-18 11:36:57 -0700120 * <p>
Philip Milne87260842014-05-22 13:56:03 -0700121 * The flexibility of a view is therefore influenced by its alignment which is,
122 * in turn, typically defined by setting the
123 * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
124 * If either a weight or alignment were defined along a given axis then the component
125 * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
Philip Milnef6679c82011-09-18 11:36:57 -0700126 * the component is instead assumed to be <em>inflexible</em>.
127 * <p>
128 * Multiple components in the same row or column group are
129 * considered to act in <em>parallel</em>. Such a
Philip Milne899d5922011-07-21 11:39:37 -0700130 * group is flexible only if <em>all</em> of the components
131 * within it are flexible. Row and column groups that sit either side of a common boundary
132 * are instead considered to act in <em>series</em>. The composite group made of these two
133 * elements is flexible if <em>one</em> of its elements is flexible.
134 * <p>
135 * To make a column stretch, make sure all of the components inside it define a
Philip Milne87260842014-05-22 13:56:03 -0700136 * weight or a gravity. To prevent a column from stretching, ensure that one of the components
137 * in the column does not define a weight or a gravity.
Philip Milne3f8956d2011-05-13 17:29:00 +0100138 * <p>
Philip Milnef6679c82011-09-18 11:36:57 -0700139 * When the principle of flexibility does not provide complete disambiguation,
140 * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
Philip Milne87260842014-05-22 13:56:03 -0700141 * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
142 * parameters as a constraint in the a set of variables that define the grid-lines along a
143 * given axis. During layout, GridLayout solves the constraints so as to return the unique
144 * solution to those constraints for which all variables are less-than-or-equal-to
145 * the corresponding value in any other valid solution.
Philip Milnef6679c82011-09-18 11:36:57 -0700146 *
Philip Milnea8416442013-02-25 10:49:39 -0800147 * <h4>Interpretation of GONE</h4>
148 *
149 * For layout purposes, GridLayout treats views whose visibility status is
150 * {@link View#GONE GONE}, as having zero width and height. This is subtly different from
151 * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked
152 * view was alone in a column, that column would itself collapse to zero width if and only if
153 * no gravity was defined on the view. If gravity was defined, then the gone-marked
154 * view has no effect on the layout and the container should be laid out as if the view
Yigit Boyar6dafd872015-03-05 13:59:56 -0800155 * had never been added to it. GONE views are taken to have zero weight during excess space
156 * distribution.
157 * <p>
Philip Milnea8416442013-02-25 10:49:39 -0800158 * These statements apply equally to rows as well as columns, and to groups of rows or columns.
159 *
Philip Milne3f8956d2011-05-13 17:29:00 +0100160 * <p>
161 * See {@link GridLayout.LayoutParams} for a full description of the
162 * layout parameters used by GridLayout.
163 *
164 * @attr ref android.R.styleable#GridLayout_orientation
165 * @attr ref android.R.styleable#GridLayout_rowCount
166 * @attr ref android.R.styleable#GridLayout_columnCount
167 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
168 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
169 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
170 */
Philip Milneedd69512012-03-14 17:21:33 -0700171@RemoteView
Philip Milne3f8956d2011-05-13 17:29:00 +0100172public class GridLayout extends ViewGroup {
173
174 // Public constants
175
Tor Norbyed9273d62013-05-30 15:59:53 -0700176 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700177 @IntDef(prefix = { "HORIZONTAL", "VERTICAL" }, value = {
178 HORIZONTAL,
179 VERTICAL
180 })
Tor Norbyed9273d62013-05-30 15:59:53 -0700181 @Retention(RetentionPolicy.SOURCE)
182 public @interface Orientation {}
183
Philip Milne3f8956d2011-05-13 17:29:00 +0100184 /**
185 * The horizontal orientation.
186 */
187 public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
Philip Milneaa616f32011-05-27 18:38:01 -0700188
Philip Milne3f8956d2011-05-13 17:29:00 +0100189 /**
190 * The vertical orientation.
191 */
192 public static final int VERTICAL = LinearLayout.VERTICAL;
193
Philip Milneaa616f32011-05-27 18:38:01 -0700194 /**
195 * The constant used to indicate that a value is undefined.
196 * Fields can use this value to indicate that their values
197 * have not yet been set. Similarly, methods can return this value
198 * to indicate that there is no suitable value that the implementation
199 * can return.
200 * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
201 * intended to avoid confusion between valid values whose sign may not be known.
202 */
203 public static final int UNDEFINED = Integer.MIN_VALUE;
204
Tor Norbyed9273d62013-05-30 15:59:53 -0700205 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700206 @IntDef(prefix = { "ALIGN_" }, value = {
207 ALIGN_BOUNDS,
208 ALIGN_MARGINS
209 })
Tor Norbyed9273d62013-05-30 15:59:53 -0700210 @Retention(RetentionPolicy.SOURCE)
211 public @interface AlignmentMode {}
212
Philip Milne1e548252011-06-16 19:02:33 -0700213 /**
214 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
215 * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
216 * is made between the edges of each component's raw
217 * view boundary: i.e. the area delimited by the component's:
218 * {@link android.view.View#getTop() top},
219 * {@link android.view.View#getLeft() left},
220 * {@link android.view.View#getBottom() bottom} and
221 * {@link android.view.View#getRight() right} properties.
222 * <p>
223 * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode,
224 * children that belong to a row group that uses {@link #TOP} alignment will
225 * all return the same value when their {@link android.view.View#getTop()}
226 * method is called.
227 *
228 * @see #setAlignmentMode(int)
229 */
230 public static final int ALIGN_BOUNDS = 0;
231
232 /**
233 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
234 * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS},
235 * the bounds of each view are extended outwards, according
236 * to their margins, before the edges of the resulting rectangle are aligned.
237 * <p>
238 * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode,
239 * the quantity {@code top - layoutParams.topMargin} is the same for all children that
240 * belong to a row group that uses {@link #TOP} alignment.
241 *
242 * @see #setAlignmentMode(int)
243 */
244 public static final int ALIGN_MARGINS = 1;
245
Philip Milne3f8956d2011-05-13 17:29:00 +0100246 // Misc constants
247
Philip Milnef6679c82011-09-18 11:36:57 -0700248 static final int MAX_SIZE = 100000;
249 static final int DEFAULT_CONTAINER_MARGIN = 0;
Philip Milned7dd8902012-01-26 16:55:30 -0800250 static final int UNINITIALIZED_HASH = 0;
Philip Milnea2353622013-03-05 12:19:15 -0800251 static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName());
252 static final Printer NO_PRINTER = new Printer() {
253 @Override
254 public void println(String x) {
255 }
256 };
Philip Milne3f8956d2011-05-13 17:29:00 +0100257
258 // Defaults
259
260 private static final int DEFAULT_ORIENTATION = HORIZONTAL;
261 private static final int DEFAULT_COUNT = UNDEFINED;
262 private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
Philip Milne899d5922011-07-21 11:39:37 -0700263 private static final boolean DEFAULT_ORDER_PRESERVED = true;
Philip Milne1e548252011-06-16 19:02:33 -0700264 private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
Philip Milne3f8956d2011-05-13 17:29:00 +0100265
266 // TypedArray indices
267
Philip Milneb0ce49b2011-07-15 15:39:07 -0700268 private static final int ORIENTATION = R.styleable.GridLayout_orientation;
269 private static final int ROW_COUNT = R.styleable.GridLayout_rowCount;
270 private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount;
271 private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins;
272 private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode;
273 private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved;
274 private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +0100275
276 // Instance variables
277
Adam Powell465ea742013-08-29 14:56:51 -0700278 final Axis mHorizontalAxis = new Axis(true);
279 final Axis mVerticalAxis = new Axis(false);
280 int mOrientation = DEFAULT_ORIENTATION;
281 boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
282 int mAlignmentMode = DEFAULT_ALIGNMENT_MODE;
283 int mDefaultGap;
284 int mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
285 Printer mPrinter = LOG_PRINTER;
Philip Milneaa616f32011-05-27 18:38:01 -0700286
Philip Milne3f8956d2011-05-13 17:29:00 +0100287 // Constructors
288
Alan Viveretted6479ec2013-09-10 17:03:02 -0700289 public GridLayout(Context context) {
290 this(context, null);
291 }
292
293 public GridLayout(Context context, AttributeSet attrs) {
294 this(context, attrs, 0);
295 }
296
Alan Viverette617feb92013-09-09 18:09:13 -0700297 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
298 this(context, attrs, defStyleAttr, 0);
299 }
300
301 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
302 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell465ea742013-08-29 14:56:51 -0700303 mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
Alan Viverette617feb92013-09-09 18:09:13 -0700304 final TypedArray a = context.obtainStyledAttributes(
305 attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800306 saveAttributeDataForStyleable(context, R.styleable.GridLayout,
307 attrs, a, defStyleAttr, defStyleRes);
Philip Milne3f8956d2011-05-13 17:29:00 +0100308 try {
Philip Milne1e548252011-06-16 19:02:33 -0700309 setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
310 setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
Philip Milne5125e212011-07-21 11:39:37 -0700311 setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION));
312 setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS));
313 setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE));
Philip Milne3f8956d2011-05-13 17:29:00 +0100314 setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
315 setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
316 } finally {
317 a.recycle();
318 }
319 }
320
321 // Implementation
322
323 /**
324 * Returns the current orientation.
325 *
Philip Milne7fd94872011-06-07 20:14:17 -0700326 * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100327 *
328 * @see #setOrientation(int)
329 *
330 * @attr ref android.R.styleable#GridLayout_orientation
331 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700332 @Orientation
Ashley Rose55f9f922019-01-28 19:29:36 -0500333 @InspectableProperty(enumMapping = {
Ashley Rose73abdd3c2019-03-15 17:32:56 -0400334 @InspectableProperty.EnumEntry(value = HORIZONTAL, name = "horizontal"),
335 @InspectableProperty.EnumEntry(value = VERTICAL, name = "vertical")
Ashley Rose55f9f922019-01-28 19:29:36 -0500336 })
Philip Milne3f8956d2011-05-13 17:29:00 +0100337 public int getOrientation() {
Adam Powell465ea742013-08-29 14:56:51 -0700338 return mOrientation;
Philip Milne3f8956d2011-05-13 17:29:00 +0100339 }
340
341 /**
Philip Milneb65408f2012-05-21 10:44:46 -0700342 *
343 * GridLayout uses the orientation property for two purposes:
344 * <ul>
345 * <li>
346 * To control the 'direction' in which default row/column indices are generated
347 * when they are not specified in a component's layout parameters.
348 * </li>
349 * <li>
350 * To control which axis should be processed first during the layout operation:
351 * when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first.
352 * </li>
353 * </ul>
354 *
355 * The order in which axes are laid out is important if, for example, the height of
356 * one of GridLayout's children is dependent on its width - and its width is, in turn,
357 * dependent on the widths of other components.
358 * <p>
359 * If your layout contains a {@link TextView} (or derivative:
360 * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is
361 * in multi-line mode (the default) it is normally best to leave GridLayout's
362 * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of
363 * deriving its height for a given width, but not the other way around.
364 * <p>
365 * Other than the effects above, orientation does not affect the actual layout operation of
366 * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if
367 * the height of the intended layout greatly exceeds its width.
Philip Milne7fd94872011-06-07 20:14:17 -0700368 * <p>
369 * The default value of this property is {@link #HORIZONTAL}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100370 *
Philip Milne7fd94872011-06-07 20:14:17 -0700371 * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100372 *
373 * @see #getOrientation()
374 *
375 * @attr ref android.R.styleable#GridLayout_orientation
376 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700377 public void setOrientation(@Orientation int orientation) {
Adam Powell465ea742013-08-29 14:56:51 -0700378 if (this.mOrientation != orientation) {
379 this.mOrientation = orientation;
Philip Milnef6679c82011-09-18 11:36:57 -0700380 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100381 requestLayout();
382 }
383 }
384
385 /**
386 * Returns the current number of rows. This is either the last value that was set
387 * with {@link #setRowCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700388 * value of each the upper bounds defined in {@link LayoutParams#rowSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100389 *
390 * @return the current number of rows
391 *
392 * @see #setRowCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700393 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100394 *
395 * @attr ref android.R.styleable#GridLayout_rowCount
396 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500397 @InspectableProperty
Philip Milne3f8956d2011-05-13 17:29:00 +0100398 public int getRowCount() {
Adam Powell465ea742013-08-29 14:56:51 -0700399 return mVerticalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100400 }
401
402 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700403 * RowCount is used only to generate default row/column indices when
404 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100405 *
Philip Milne7fd94872011-06-07 20:14:17 -0700406 * @param rowCount the number of rows
Philip Milne3f8956d2011-05-13 17:29:00 +0100407 *
408 * @see #getRowCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700409 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100410 *
411 * @attr ref android.R.styleable#GridLayout_rowCount
412 */
413 public void setRowCount(int rowCount) {
Adam Powell465ea742013-08-29 14:56:51 -0700414 mVerticalAxis.setCount(rowCount);
Philip Milnef6679c82011-09-18 11:36:57 -0700415 invalidateStructure();
416 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100417 }
418
419 /**
420 * Returns the current number of columns. This is either the last value that was set
421 * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700422 * value of each the upper bounds defined in {@link LayoutParams#columnSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100423 *
424 * @return the current number of columns
425 *
426 * @see #setColumnCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700427 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100428 *
429 * @attr ref android.R.styleable#GridLayout_columnCount
430 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500431 @InspectableProperty
Philip Milne3f8956d2011-05-13 17:29:00 +0100432 public int getColumnCount() {
Adam Powell465ea742013-08-29 14:56:51 -0700433 return mHorizontalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100434 }
435
436 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700437 * ColumnCount is used only to generate default column/column indices when
438 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100439 *
440 * @param columnCount the number of columns.
441 *
442 * @see #getColumnCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700443 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100444 *
445 * @attr ref android.R.styleable#GridLayout_columnCount
446 */
447 public void setColumnCount(int columnCount) {
Adam Powell465ea742013-08-29 14:56:51 -0700448 mHorizontalAxis.setCount(columnCount);
Philip Milnef6679c82011-09-18 11:36:57 -0700449 invalidateStructure();
450 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100451 }
452
453 /**
454 * Returns whether or not this GridLayout will allocate default margins when no
455 * corresponding layout parameters are defined.
456 *
Philip Milne7fd94872011-06-07 20:14:17 -0700457 * @return {@code true} if default margins should be allocated
Philip Milne3f8956d2011-05-13 17:29:00 +0100458 *
459 * @see #setUseDefaultMargins(boolean)
460 *
461 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
462 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500463 @InspectableProperty
Philip Milne3f8956d2011-05-13 17:29:00 +0100464 public boolean getUseDefaultMargins() {
Adam Powell465ea742013-08-29 14:56:51 -0700465 return mUseDefaultMargins;
Philip Milne3f8956d2011-05-13 17:29:00 +0100466 }
467
468 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700469 * When {@code true}, GridLayout allocates default margins around children
Philip Milne3f8956d2011-05-13 17:29:00 +0100470 * based on the child's visual characteristics. Each of the
471 * margins so defined may be independently overridden by an assignment
472 * to the appropriate layout parameter.
473 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700474 * When {@code false}, the default value of all margins is zero.
Philip Milneaa616f32011-05-27 18:38:01 -0700475 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700476 * When setting to {@code true}, consider setting the value of the
Philip Milne1e548252011-06-16 19:02:33 -0700477 * {@link #setAlignmentMode(int) alignmentMode}
478 * property to {@link #ALIGN_BOUNDS}.
Philip Milne7fd94872011-06-07 20:14:17 -0700479 * <p>
480 * The default value of this property is {@code false}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100481 *
Philip Milne7fd94872011-06-07 20:14:17 -0700482 * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
Philip Milne3f8956d2011-05-13 17:29:00 +0100483 *
484 * @see #getUseDefaultMargins()
Philip Milne1e548252011-06-16 19:02:33 -0700485 * @see #setAlignmentMode(int)
Philip Milne3f8956d2011-05-13 17:29:00 +0100486 *
Aurimas Liutikase701dc12018-06-01 16:04:37 -0700487 * @see ViewGroup.MarginLayoutParams#leftMargin
488 * @see ViewGroup.MarginLayoutParams#topMargin
489 * @see ViewGroup.MarginLayoutParams#rightMargin
490 * @see ViewGroup.MarginLayoutParams#bottomMargin
Philip Milne3f8956d2011-05-13 17:29:00 +0100491 *
492 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
493 */
494 public void setUseDefaultMargins(boolean useDefaultMargins) {
Adam Powell465ea742013-08-29 14:56:51 -0700495 this.mUseDefaultMargins = useDefaultMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700496 requestLayout();
497 }
498
499 /**
Philip Milne1e548252011-06-16 19:02:33 -0700500 * Returns the alignment mode.
Philip Milneaa616f32011-05-27 18:38:01 -0700501 *
Philip Milne1e548252011-06-16 19:02:33 -0700502 * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700503 *
Philip Milne1e548252011-06-16 19:02:33 -0700504 * @see #ALIGN_BOUNDS
505 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700506 *
Philip Milne1e548252011-06-16 19:02:33 -0700507 * @see #setAlignmentMode(int)
508 *
509 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700510 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700511 @AlignmentMode
Ashley Rose55f9f922019-01-28 19:29:36 -0500512 @InspectableProperty(enumMapping = {
Ashley Rose73abdd3c2019-03-15 17:32:56 -0400513 @InspectableProperty.EnumEntry(value = ALIGN_BOUNDS, name = "alignBounds"),
514 @InspectableProperty.EnumEntry(value = ALIGN_MARGINS, name = "alignMargins"),
Ashley Rose55f9f922019-01-28 19:29:36 -0500515 })
Philip Milne1e548252011-06-16 19:02:33 -0700516 public int getAlignmentMode() {
Adam Powell465ea742013-08-29 14:56:51 -0700517 return mAlignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700518 }
519
520 /**
Philip Milne1e548252011-06-16 19:02:33 -0700521 * Sets the alignment mode to be used for all of the alignments between the
522 * children of this container.
Philip Milne7fd94872011-06-07 20:14:17 -0700523 * <p>
Philip Milne1e548252011-06-16 19:02:33 -0700524 * The default value of this property is {@link #ALIGN_MARGINS}.
Philip Milneaa616f32011-05-27 18:38:01 -0700525 *
Philip Milne1e548252011-06-16 19:02:33 -0700526 * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700527 *
Philip Milne1e548252011-06-16 19:02:33 -0700528 * @see #ALIGN_BOUNDS
529 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700530 *
Philip Milne1e548252011-06-16 19:02:33 -0700531 * @see #getAlignmentMode()
532 *
533 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700534 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700535 public void setAlignmentMode(@AlignmentMode int alignmentMode) {
Adam Powell465ea742013-08-29 14:56:51 -0700536 this.mAlignmentMode = alignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700537 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100538 }
539
540 /**
541 * Returns whether or not row boundaries are ordered by their grid indices.
542 *
Philip Milne7fd94872011-06-07 20:14:17 -0700543 * @return {@code true} if row boundaries must appear in the order of their indices,
544 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100545 *
546 * @see #setRowOrderPreserved(boolean)
547 *
548 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
549 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500550 @InspectableProperty
Philip Milne3f8956d2011-05-13 17:29:00 +0100551 public boolean isRowOrderPreserved() {
Adam Powell465ea742013-08-29 14:56:51 -0700552 return mVerticalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100553 }
554
555 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700556 * When this property is {@code true}, GridLayout is forced to place the row boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700557 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100558 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700559 * When this property is {@code false} GridLayout is at liberty to place the vertical row
560 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700561 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700562 * The default value of this property is {@code true}.
Philip Milne7fd94872011-06-07 20:14:17 -0700563
564 * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
565 * of row boundaries
Philip Milne3f8956d2011-05-13 17:29:00 +0100566 *
567 * @see #isRowOrderPreserved()
568 *
569 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
570 */
571 public void setRowOrderPreserved(boolean rowOrderPreserved) {
Adam Powell465ea742013-08-29 14:56:51 -0700572 mVerticalAxis.setOrderPreserved(rowOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700573 invalidateStructure();
574 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100575 }
576
577 /**
578 * Returns whether or not column boundaries are ordered by their grid indices.
579 *
Philip Milne7fd94872011-06-07 20:14:17 -0700580 * @return {@code true} if column boundaries must appear in the order of their indices,
581 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100582 *
583 * @see #setColumnOrderPreserved(boolean)
584 *
585 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
586 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500587 @InspectableProperty
Philip Milne3f8956d2011-05-13 17:29:00 +0100588 public boolean isColumnOrderPreserved() {
Adam Powell465ea742013-08-29 14:56:51 -0700589 return mHorizontalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100590 }
591
592 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700593 * When this property is {@code true}, GridLayout is forced to place the column boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700594 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100595 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700596 * When this property is {@code false} GridLayout is at liberty to place the horizontal column
597 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700598 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700599 * The default value of this property is {@code true}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100600 *
Philip Milne7fd94872011-06-07 20:14:17 -0700601 * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
Philip Milne3f8956d2011-05-13 17:29:00 +0100602 * of column boundaries.
603 *
604 * @see #isColumnOrderPreserved()
605 *
606 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
607 */
608 public void setColumnOrderPreserved(boolean columnOrderPreserved) {
Adam Powell465ea742013-08-29 14:56:51 -0700609 mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700610 invalidateStructure();
611 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100612 }
613
Philip Milne211d0332012-04-24 14:45:14 -0700614 /**
615 * Return the printer that will log diagnostics from this layout.
616 *
617 * @see #setPrinter(android.util.Printer)
618 *
619 * @return the printer associated with this view
Adam Powell465ea742013-08-29 14:56:51 -0700620 *
621 * @hide
Philip Milne211d0332012-04-24 14:45:14 -0700622 */
623 public Printer getPrinter() {
Adam Powell465ea742013-08-29 14:56:51 -0700624 return mPrinter;
Philip Milne211d0332012-04-24 14:45:14 -0700625 }
626
627 /**
628 * Set the printer that will log diagnostics from this layout.
629 * The default value is created by {@link android.util.LogPrinter}.
630 *
631 * @param printer the printer associated with this layout
632 *
633 * @see #getPrinter()
Adam Powell465ea742013-08-29 14:56:51 -0700634 *
635 * @hide
Philip Milne211d0332012-04-24 14:45:14 -0700636 */
637 public void setPrinter(Printer printer) {
Adam Powell465ea742013-08-29 14:56:51 -0700638 this.mPrinter = (printer == null) ? NO_PRINTER : printer;
Philip Milne211d0332012-04-24 14:45:14 -0700639 }
640
Philip Milne5125e212011-07-21 11:39:37 -0700641 // Static utility methods
642
Philip Milnef6679c82011-09-18 11:36:57 -0700643 static int max2(int[] a, int valueIfEmpty) {
Philip Milne51f17d52011-06-13 10:44:49 -0700644 int result = valueIfEmpty;
645 for (int i = 0, N = a.length; i < N; i++) {
646 result = Math.max(result, a[i]);
647 }
648 return result;
649 }
650
Philip Milne899d5922011-07-21 11:39:37 -0700651 @SuppressWarnings("unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -0700652 static <T> T[] append(T[] a, T[] b) {
Philip Milne48b55242011-06-29 11:09:45 -0700653 T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
654 System.arraycopy(a, 0, result, 0, a.length);
655 System.arraycopy(b, 0, result, a.length, b.length);
Philip Milne3f8956d2011-05-13 17:29:00 +0100656 return result;
657 }
658
Philip Milnef6679c82011-09-18 11:36:57 -0700659 static Alignment getAlignment(int gravity, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -0700660 int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
661 int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
662 int flags = (gravity & mask) >> shift;
663 switch (flags) {
664 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
Philip Milne1557fd72012-04-04 23:41:34 -0700665 return horizontal ? LEFT : TOP;
Philip Milne5125e212011-07-21 11:39:37 -0700666 case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
Philip Milne1557fd72012-04-04 23:41:34 -0700667 return horizontal ? RIGHT : BOTTOM;
Philip Milne5125e212011-07-21 11:39:37 -0700668 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
669 return FILL;
670 case AXIS_SPECIFIED:
671 return CENTER;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800672 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION):
673 return START;
674 case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION):
675 return END;
Philip Milne5125e212011-07-21 11:39:37 -0700676 default:
677 return UNDEFINED_ALIGNMENT;
678 }
679 }
680
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700681 /** @noinspection UnusedParameters*/
Philip Milne1fd16372011-06-21 14:57:47 -0700682 private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
Philip Milne899d5922011-07-21 11:39:37 -0700683 if (c.getClass() == Space.class) {
684 return 0;
685 }
Adam Powell465ea742013-08-29 14:56:51 -0700686 return mDefaultGap / 2;
Philip Milne3f8956d2011-05-13 17:29:00 +0100687 }
688
Philip Milne1fd16372011-06-21 14:57:47 -0700689 private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
Philip Milne7b757812012-09-19 18:13:44 -0700690 return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100691 }
692
Philip Milne7a23b492012-04-24 22:12:36 -0700693 private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
Adam Powell465ea742013-08-29 14:56:51 -0700694 if (!mUseDefaultMargins) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100695 return 0;
696 }
Philip Milne93cd6a62011-07-12 14:49:45 -0700697 Spec spec = horizontal ? p.columnSpec : p.rowSpec;
Adam Powell465ea742013-08-29 14:56:51 -0700698 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne93cd6a62011-07-12 14:49:45 -0700699 Interval span = spec.span;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800700 boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading;
701 boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
Philip Milne3f8956d2011-05-13 17:29:00 +0100702
Philip Milne1fd16372011-06-21 14:57:47 -0700703 return getDefaultMargin(c, isAtEdge, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100704 }
705
Philip Milnef6679c82011-09-18 11:36:57 -0700706 int getMargin1(View view, boolean horizontal, boolean leading) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100707 LayoutParams lp = getLayoutParams(view);
708 int margin = horizontal ?
Philip Milneaa616f32011-05-27 18:38:01 -0700709 (leading ? lp.leftMargin : lp.rightMargin) :
710 (leading ? lp.topMargin : lp.bottomMargin);
Philip Milne7a23b492012-04-24 22:12:36 -0700711 return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin;
Philip Milne1fd16372011-06-21 14:57:47 -0700712 }
713
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700714 private int getMargin(View view, boolean horizontal, boolean leading) {
Adam Powell465ea742013-08-29 14:56:51 -0700715 if (mAlignmentMode == ALIGN_MARGINS) {
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700716 return getMargin1(view, horizontal, leading);
717 } else {
Adam Powell465ea742013-08-29 14:56:51 -0700718 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700719 int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
720 LayoutParams lp = getLayoutParams(view);
721 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
722 int index = leading ? spec.span.min : spec.span.max;
723 return margins[index];
724 }
725 }
726
Philip Milne1fd16372011-06-21 14:57:47 -0700727 private int getTotalMargin(View child, boolean horizontal) {
728 return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
Philip Milne3f8956d2011-05-13 17:29:00 +0100729 }
730
Philip Milne899d5922011-07-21 11:39:37 -0700731 private static boolean fits(int[] a, int value, int start, int end) {
732 if (end > a.length) {
733 return false;
734 }
735 for (int i = start; i < end; i++) {
736 if (a[i] > value) {
737 return false;
738 }
739 }
740 return true;
741 }
742
743 private static void procrusteanFill(int[] a, int start, int end, int value) {
744 int length = a.length;
745 Arrays.fill(a, Math.min(start, length), Math.min(end, length), value);
746 }
747
748 private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) {
749 lp.setRowSpecSpan(new Interval(row, row + rowSpan));
750 lp.setColumnSpecSpan(new Interval(col, col + colSpan));
751 }
752
753 // Logic to avert infinite loops by ensuring that the cells can be placed somewhere.
754 private static int clip(Interval minorRange, boolean minorWasDefined, int count) {
755 int size = minorRange.size();
756 if (count == 0) {
757 return size;
758 }
759 int min = minorWasDefined ? min(minorRange.min, count) : 0;
760 return min(size, count - min);
761 }
762
Philip Milnef4748702011-06-09 18:30:32 -0700763 // install default indices for cells that don't define them
Philip Milne3f8956d2011-05-13 17:29:00 +0100764 private void validateLayoutParams() {
Adam Powell465ea742013-08-29 14:56:51 -0700765 final boolean horizontal = (mOrientation == HORIZONTAL);
766 final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Jim Miller782c04b2012-05-02 15:45:23 -0700767 final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
Philip Milne3f8956d2011-05-13 17:29:00 +0100768
Philip Milne899d5922011-07-21 11:39:37 -0700769 int major = 0;
770 int minor = 0;
771 int[] maxSizes = new int[count];
772
773 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milned7dd8902012-01-26 16:55:30 -0800774 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
Philip Milne899d5922011-07-21 11:39:37 -0700775
Philip Milnef6679c82011-09-18 11:36:57 -0700776 final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
777 final Interval majorRange = majorSpec.span;
778 final boolean majorWasDefined = majorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700779 final int majorSpan = majorRange.size();
780 if (majorWasDefined) {
781 major = majorRange.min;
Philip Milnef4748702011-06-09 18:30:32 -0700782 }
783
Philip Milnef6679c82011-09-18 11:36:57 -0700784 final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
785 final Interval minorRange = minorSpec.span;
786 final boolean minorWasDefined = minorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700787 final int minorSpan = clip(minorRange, minorWasDefined, count);
788 if (minorWasDefined) {
789 minor = minorRange.min;
790 }
Philip Milnef4748702011-06-09 18:30:32 -0700791
Philip Milne899d5922011-07-21 11:39:37 -0700792 if (count != 0) {
793 // Find suitable row/col values when at least one is undefined.
794 if (!majorWasDefined || !minorWasDefined) {
795 while (!fits(maxSizes, major, minor, minor + minorSpan)) {
796 if (minorWasDefined) {
797 major++;
798 } else {
799 if (minor + minorSpan <= count) {
800 minor++;
801 } else {
802 minor = 0;
803 major++;
804 }
Philip Milnef4748702011-06-09 18:30:32 -0700805 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100806 }
807 }
Philip Milne899d5922011-07-21 11:39:37 -0700808 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan);
Philip Milne3f8956d2011-05-13 17:29:00 +0100809 }
Philip Milne899d5922011-07-21 11:39:37 -0700810
811 if (horizontal) {
812 setCellGroup(lp, major, majorSpan, minor, minorSpan);
813 } else {
814 setCellGroup(lp, minor, minorSpan, major, majorSpan);
815 }
816
817 minor = minor + minorSpan;
818 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100819 }
820
821 private void invalidateStructure() {
Adam Powell465ea742013-08-29 14:56:51 -0700822 mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
823 mHorizontalAxis.invalidateStructure();
824 mVerticalAxis.invalidateStructure();
Philip Milne899d5922011-07-21 11:39:37 -0700825 // This can end up being done twice. Better twice than not at all.
Philip Milne3f8956d2011-05-13 17:29:00 +0100826 invalidateValues();
827 }
828
829 private void invalidateValues() {
Philip Milneaa616f32011-05-27 18:38:01 -0700830 // Need null check because requestLayout() is called in View's initializer,
831 // before we are set up.
Adam Powell465ea742013-08-29 14:56:51 -0700832 if (mHorizontalAxis != null && mVerticalAxis != null) {
833 mHorizontalAxis.invalidateValues();
834 mVerticalAxis.invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -0700835 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100836 }
837
Philip Milned7dd8902012-01-26 16:55:30 -0800838 /** @hide */
839 @Override
840 protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) {
841 super.onSetLayoutParams(child, layoutParams);
Philip Milne0f57cea2012-05-12 09:34:25 -0700842
843 if (!checkLayoutParams(layoutParams)) {
844 handleInvalidParams("supplied LayoutParams are of the wrong type");
845 }
846
Philip Milned7dd8902012-01-26 16:55:30 -0800847 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100848 }
849
Philip Milnef6679c82011-09-18 11:36:57 -0700850 final LayoutParams getLayoutParams(View c) {
Philip Milned7dd8902012-01-26 16:55:30 -0800851 return (LayoutParams) c.getLayoutParams();
Philip Milne3f8956d2011-05-13 17:29:00 +0100852 }
853
Philip Milne0f57cea2012-05-12 09:34:25 -0700854 private static void handleInvalidParams(String msg) {
855 throw new IllegalArgumentException(msg + ". ");
856 }
857
858 private void checkLayoutParams(LayoutParams lp, boolean horizontal) {
859 String groupName = horizontal ? "column" : "row";
860 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
861 Interval span = spec.span;
862 if (span.min != UNDEFINED && span.min < 0) {
863 handleInvalidParams(groupName + " indices must be positive");
864 }
Adam Powell465ea742013-08-29 14:56:51 -0700865 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne0f57cea2012-05-12 09:34:25 -0700866 int count = axis.definedCount;
867 if (count != UNDEFINED) {
868 if (span.max > count) {
869 handleInvalidParams(groupName +
870 " indices (start + span) mustn't exceed the " + groupName + " count");
871 }
872 if (span.size() > count) {
873 handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count");
874 }
875 }
876 }
877
878 @Override
879 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
880 if (!(p instanceof LayoutParams)) {
881 return false;
882 }
883 LayoutParams lp = (LayoutParams) p;
884
885 checkLayoutParams(lp, true);
886 checkLayoutParams(lp, false);
887
888 return true;
889 }
890
Philip Milne3f8956d2011-05-13 17:29:00 +0100891 @Override
892 protected LayoutParams generateDefaultLayoutParams() {
893 return new LayoutParams();
894 }
895
896 @Override
897 public LayoutParams generateLayoutParams(AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -0700898 return new LayoutParams(getContext(), attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +0100899 }
900
901 @Override
Yigit Boyar885c50b2016-03-22 16:53:42 -0700902 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
Yigit Boyar2dd20a62016-08-01 17:42:28 -0700903 if (sPreserveMarginParamsInLayoutParamConversion) {
904 if (lp instanceof LayoutParams) {
905 return new LayoutParams((LayoutParams) lp);
906 } else if (lp instanceof MarginLayoutParams) {
907 return new LayoutParams((MarginLayoutParams) lp);
908 }
Yigit Boyar885c50b2016-03-22 16:53:42 -0700909 }
Yigit Boyar2dd20a62016-08-01 17:42:28 -0700910 return new LayoutParams(lp);
Philip Milne3f8956d2011-05-13 17:29:00 +0100911 }
912
913 // Draw grid
914
915 private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800916 if (isLayoutRtl()) {
917 int width = getWidth();
Philip Milne7b757812012-09-19 18:13:44 -0700918 graphics.drawLine(width - x1, y1, width - x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800919 } else {
Philip Milne7b757812012-09-19 18:13:44 -0700920 graphics.drawLine(x1, y1, x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800921 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100922 }
923
Alan Viverette2d7771c2018-01-31 17:04:31 -0500924 /**
925 * @hide
926 */
Philip Milne10ca24a2012-04-23 15:38:27 -0700927 @Override
Philip Milne7b757812012-09-19 18:13:44 -0700928 protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700929 // Apply defaults, so as to remove UNDEFINED values
930 LayoutParams lp = new LayoutParams();
931 for (int i = 0; i < getChildCount(); i++) {
932 View c = getChildAt(i);
933 lp.setMargins(
Philip Milne7b757812012-09-19 18:13:44 -0700934 getMargin1(c, true, true),
935 getMargin1(c, false, true),
936 getMargin1(c, true, false),
937 getMargin1(c, false, false));
938 lp.onDebugDraw(c, canvas, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700939 }
Philip Milneb5599762011-08-05 11:04:36 -0700940 }
941
Alan Viverette2d7771c2018-01-31 17:04:31 -0500942 /**
943 * @hide
944 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100945 @Override
Philip Milne10ca24a2012-04-23 15:38:27 -0700946 protected void onDebugDraw(Canvas canvas) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700947 Paint paint = new Paint();
948 paint.setStyle(Paint.Style.STROKE);
949 paint.setColor(Color.argb(50, 255, 255, 255));
Philip Milne3f8956d2011-05-13 17:29:00 +0100950
Philip Milne7b757812012-09-19 18:13:44 -0700951 Insets insets = getOpticalInsets();
952
953 int top = getPaddingTop() + insets.top;
954 int left = getPaddingLeft() + insets.left;
955 int right = getWidth() - getPaddingRight() - insets.right;
956 int bottom = getHeight() - getPaddingBottom() - insets.bottom;
957
Adam Powell465ea742013-08-29 14:56:51 -0700958 int[] xs = mHorizontalAxis.locations;
Philip Milne10ca24a2012-04-23 15:38:27 -0700959 if (xs != null) {
960 for (int i = 0, length = xs.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700961 int x = left + xs[i];
962 drawLine(canvas, x, top, x, bottom, paint);
Philip Milne3f8956d2011-05-13 17:29:00 +0100963 }
964 }
Philip Milne10ca24a2012-04-23 15:38:27 -0700965
Adam Powell465ea742013-08-29 14:56:51 -0700966 int[] ys = mVerticalAxis.locations;
Philip Milne10ca24a2012-04-23 15:38:27 -0700967 if (ys != null) {
968 for (int i = 0, length = ys.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700969 int y = top + ys[i];
970 drawLine(canvas, left, y, right, y, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700971 }
972 }
973
974 super.onDebugDraw(canvas);
Philip Milne3f8956d2011-05-13 17:29:00 +0100975 }
976
Philip Milne3f8956d2011-05-13 17:29:00 +0100977 @Override
Adam Powell6690d012015-06-17 16:41:56 -0700978 public void onViewAdded(View child) {
Philip Milnef51d91c2011-07-18 16:12:19 -0700979 super.onViewAdded(child);
Philip Milne3f8956d2011-05-13 17:29:00 +0100980 invalidateStructure();
981 }
982
983 @Override
Adam Powell6690d012015-06-17 16:41:56 -0700984 public void onViewRemoved(View child) {
Philip Milnef51d91c2011-07-18 16:12:19 -0700985 super.onViewRemoved(child);
Philip Milneb0ce49b2011-07-15 15:39:07 -0700986 invalidateStructure();
987 }
988
Philip Milne350f0a62011-07-19 14:00:56 -0700989 /**
990 * We need to call invalidateStructure() when a child's GONE flag changes state.
991 * This implementation is a catch-all, invalidating on any change in the visibility flags.
992 *
993 * @hide
994 */
995 @Override
Chet Haase0d299362012-01-26 10:51:48 -0800996 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
997 super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
998 if (oldVisibility == GONE || newVisibility == GONE) {
Philip Milnea8416442013-02-25 10:49:39 -0800999 invalidateStructure();
Chet Haase0d299362012-01-26 10:51:48 -08001000 }
Philip Milne350f0a62011-07-19 14:00:56 -07001001 }
1002
Philip Milned7dd8902012-01-26 16:55:30 -08001003 private int computeLayoutParamsHashCode() {
1004 int result = 1;
1005 for (int i = 0, N = getChildCount(); i < N; i++) {
1006 View c = getChildAt(i);
1007 if (c.getVisibility() == View.GONE) continue;
1008 LayoutParams lp = (LayoutParams) c.getLayoutParams();
1009 result = 31 * result + lp.hashCode();
1010 }
1011 return result;
Philip Milneb3a8c542011-06-20 16:02:59 -07001012 }
1013
Philip Milneedd69512012-03-14 17:21:33 -07001014 private void consistencyCheck() {
Adam Powell465ea742013-08-29 14:56:51 -07001015 if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) {
Philip Milneedd69512012-03-14 17:21:33 -07001016 validateLayoutParams();
Adam Powell465ea742013-08-29 14:56:51 -07001017 mLastLayoutParamsHashCode = computeLayoutParamsHashCode();
1018 } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
1019 mPrinter.println("The fields of some layout parameters were modified in between "
Philip Milne211d0332012-04-24 14:45:14 -07001020 + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
Philip Milneedd69512012-03-14 17:21:33 -07001021 invalidateStructure();
1022 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001023 }
1024 }
1025
1026 // Measurement
1027
Philip Milnee0b85cd2013-04-12 14:38:34 -07001028 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -07001029 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
Philip Milneedd69512012-03-14 17:21:33 -07001030 int childWidth, int childHeight) {
Philip Milne4a145d72011-09-29 14:14:25 -07001031 int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -07001032 getTotalMargin(child, true), childWidth);
Philip Milne4a145d72011-09-29 14:14:25 -07001033 int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -07001034 getTotalMargin(child, false), childHeight);
Philip Milne4a145d72011-09-29 14:14:25 -07001035 child.measure(childWidthSpec, childHeightSpec);
Philip Milneb3a8c542011-06-20 16:02:59 -07001036 }
1037
Philip Milnee0b85cd2013-04-12 14:38:34 -07001038 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -07001039 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001040 for (int i = 0, N = getChildCount(); i < N; i++) {
1041 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001042 if (c.getVisibility() == View.GONE) continue;
Philip Milne4a145d72011-09-29 14:14:25 -07001043 LayoutParams lp = getLayoutParams(c);
1044 if (firstPass) {
1045 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
1046 } else {
Adam Powell465ea742013-08-29 14:56:51 -07001047 boolean horizontal = (mOrientation == HORIZONTAL);
Philip Milneecab1172011-10-25 15:07:19 -07001048 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Yigit Boyar6dafd872015-03-05 13:59:56 -08001049 if (spec.getAbsoluteAlignment(horizontal) == FILL) {
Philip Milne4a145d72011-09-29 14:14:25 -07001050 Interval span = spec.span;
Adam Powell465ea742013-08-29 14:56:51 -07001051 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne4a145d72011-09-29 14:14:25 -07001052 int[] locations = axis.getLocations();
Philip Milneecab1172011-10-25 15:07:19 -07001053 int cellSize = locations[span.max] - locations[span.min];
1054 int viewSize = cellSize - getTotalMargin(c, horizontal);
1055 if (horizontal) {
1056 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);
Philip Milne4a145d72011-09-29 14:14:25 -07001057 } else {
Philip Milneecab1172011-10-25 15:07:19 -07001058 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);
Philip Milne4a145d72011-09-29 14:14:25 -07001059 }
1060 }
1061 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001062 }
1063 }
1064
Philip Milnee0b85cd2013-04-12 14:38:34 -07001065 static int adjust(int measureSpec, int delta) {
1066 return makeMeasureSpec(
1067 MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec));
1068 }
1069
Philip Milne3f8956d2011-05-13 17:29:00 +01001070 @Override
1071 protected void onMeasure(int widthSpec, int heightSpec) {
Philip Milneedd69512012-03-14 17:21:33 -07001072 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001073
Philip Milne4a145d72011-09-29 14:14:25 -07001074 /** If we have been called by {@link View#measure(int, int)}, one of width or height
1075 * is likely to have changed. We must invalidate if so. */
1076 invalidateValues();
Philip Milne3f8956d2011-05-13 17:29:00 +01001077
Philip Milnee0b85cd2013-04-12 14:38:34 -07001078 int hPadding = getPaddingLeft() + getPaddingRight();
1079 int vPadding = getPaddingTop() + getPaddingBottom();
Philip Milne3f8956d2011-05-13 17:29:00 +01001080
Philip Milnee0b85cd2013-04-12 14:38:34 -07001081 int widthSpecSansPadding = adjust( widthSpec, -hPadding);
1082 int heightSpecSansPadding = adjust(heightSpec, -vPadding);
1083
1084 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);
1085
1086 int widthSansPadding;
1087 int heightSansPadding;
Philip Milne4a145d72011-09-29 14:14:25 -07001088
1089 // Use the orientation property to decide which axis should be laid out first.
Adam Powell465ea742013-08-29 14:56:51 -07001090 if (mOrientation == HORIZONTAL) {
1091 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
Philip Milnee0b85cd2013-04-12 14:38:34 -07001092 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
Adam Powell465ea742013-08-29 14:56:51 -07001093 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001094 } else {
Adam Powell465ea742013-08-29 14:56:51 -07001095 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
Philip Milnee0b85cd2013-04-12 14:38:34 -07001096 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
Adam Powell465ea742013-08-29 14:56:51 -07001097 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001098 }
1099
Philip Milnee0b85cd2013-04-12 14:38:34 -07001100 int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth());
1101 int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight());
Philip Milne09e2d4d2011-06-20 10:28:18 -07001102
Philip Milne3f8956d2011-05-13 17:29:00 +01001103 setMeasuredDimension(
Philip Milnee0b85cd2013-04-12 14:38:34 -07001104 resolveSizeAndState(measuredWidth, widthSpec, 0),
Philip Milne09e2d4d2011-06-20 10:28:18 -07001105 resolveSizeAndState(measuredHeight, heightSpec, 0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001106 }
1107
Philip Milne48b55242011-06-29 11:09:45 -07001108 private int getMeasurement(View c, boolean horizontal) {
Philip Milne7b757812012-09-19 18:13:44 -07001109 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
Philip Milneaa616f32011-05-27 18:38:01 -07001110 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001111
Philip Milnef6679c82011-09-18 11:36:57 -07001112 final int getMeasurementIncludingMargin(View c, boolean horizontal) {
Philip Milned7dd8902012-01-26 16:55:30 -08001113 if (c.getVisibility() == View.GONE) {
Philip Milne5125e212011-07-21 11:39:37 -07001114 return 0;
1115 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001116 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
Philip Milne3f8956d2011-05-13 17:29:00 +01001117 }
1118
Philip Milne3f8956d2011-05-13 17:29:00 +01001119 @Override
Philip Milneaa616f32011-05-27 18:38:01 -07001120 public void requestLayout() {
1121 super.requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +01001122 invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -07001123 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001124
Philip Milneaa616f32011-05-27 18:38:01 -07001125 // Layout container
1126
1127 /**
1128 * {@inheritDoc}
1129 */
1130 /*
1131 The layout operation is implemented by delegating the heavy lifting to the
1132 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
1133 Together they compute the locations of the vertical and horizontal lines of
1134 the grid (respectively!).
1135
1136 This method is then left with the simpler task of applying margins, gravity
1137 and sizing to each child view and then placing it in its cell.
1138 */
1139 @Override
Philip Milne09e2d4d2011-06-20 10:28:18 -07001140 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Philip Milneedd69512012-03-14 17:21:33 -07001141 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001142
Philip Milne09e2d4d2011-06-20 10:28:18 -07001143 int targetWidth = right - left;
1144 int targetHeight = bottom - top;
Philip Milne3f8956d2011-05-13 17:29:00 +01001145
1146 int paddingLeft = getPaddingLeft();
1147 int paddingTop = getPaddingTop();
1148 int paddingRight = getPaddingRight();
1149 int paddingBottom = getPaddingBottom();
1150
Adam Powell465ea742013-08-29 14:56:51 -07001151 mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
1152 mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
Philip Milne3f8956d2011-05-13 17:29:00 +01001153
Adam Powell465ea742013-08-29 14:56:51 -07001154 int[] hLocations = mHorizontalAxis.getLocations();
1155 int[] vLocations = mVerticalAxis.getLocations();
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001156
Philip Milneb3a8c542011-06-20 16:02:59 -07001157 for (int i = 0, N = getChildCount(); i < N; i++) {
1158 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001159 if (c.getVisibility() == View.GONE) continue;
Philip Milneb3a8c542011-06-20 16:02:59 -07001160 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001161 Spec columnSpec = lp.columnSpec;
1162 Spec rowSpec = lp.rowSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001163
Philip Milne93cd6a62011-07-12 14:49:45 -07001164 Interval colSpan = columnSpec.span;
1165 Interval rowSpan = rowSpec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001166
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001167 int x1 = hLocations[colSpan.min];
1168 int y1 = vLocations[rowSpan.min];
Philip Milneaa616f32011-05-27 18:38:01 -07001169
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001170 int x2 = hLocations[colSpan.max];
1171 int y2 = vLocations[rowSpan.max];
Philip Milne3f8956d2011-05-13 17:29:00 +01001172
1173 int cellWidth = x2 - x1;
1174 int cellHeight = y2 - y1;
1175
Philip Milne48b55242011-06-29 11:09:45 -07001176 int pWidth = getMeasurement(c, true);
1177 int pHeight = getMeasurement(c, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001178
Yigit Boyar6dafd872015-03-05 13:59:56 -08001179 Alignment hAlign = columnSpec.getAbsoluteAlignment(true);
1180 Alignment vAlign = rowSpec.getAbsoluteAlignment(false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001181
Adam Powell465ea742013-08-29 14:56:51 -07001182 Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i);
1183 Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i);
Philip Milne7fd94872011-06-07 20:14:17 -07001184
1185 // Gravity offsets: the location of the alignment group relative to its cell group.
Philip Milne6216e872012-02-16 17:15:50 -08001186 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true));
1187 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true));
Philip Milne7fd94872011-06-07 20:14:17 -07001188
Philip Milne6216e872012-02-16 17:15:50 -08001189 int leftMargin = getMargin(c, true, true);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001190 int topMargin = getMargin(c, false, true);
Philip Milne6216e872012-02-16 17:15:50 -08001191 int rightMargin = getMargin(c, true, false);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001192 int bottomMargin = getMargin(c, false, false);
Philip Milne7fd94872011-06-07 20:14:17 -07001193
Philip Milne1557fd72012-04-04 23:41:34 -07001194 int sumMarginsX = leftMargin + rightMargin;
1195 int sumMarginsY = topMargin + bottomMargin;
Philip Milne7fd94872011-06-07 20:14:17 -07001196
Philip Milne1557fd72012-04-04 23:41:34 -07001197 // Alignment offsets: the location of the view relative to its alignment group.
1198 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
1199 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
1200
1201 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
1202 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
Philip Milne7fd94872011-06-07 20:14:17 -07001203
Philip Milne6216e872012-02-16 17:15:50 -08001204 int dx = x1 + gravityOffsetX + alignmentOffsetX;
Philip Milne3f8956d2011-05-13 17:29:00 +01001205
Philip Milne6216e872012-02-16 17:15:50 -08001206 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx :
1207 targetWidth - width - paddingRight - rightMargin - dx;
1208 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
Philip Milne3f8956d2011-05-13 17:29:00 +01001209
Philip Milne899d5922011-07-21 11:39:37 -07001210 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
1211 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
1212 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001213 c.layout(cx, cy, cx + width, cy + height);
Philip Milne3f8956d2011-05-13 17:29:00 +01001214 }
1215 }
1216
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001217 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001218 public CharSequence getAccessibilityClassName() {
1219 return GridLayout.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001220 }
1221
Philip Milne3f8956d2011-05-13 17:29:00 +01001222 // Inner classes
1223
Philip Milneaa616f32011-05-27 18:38:01 -07001224 /*
1225 This internal class houses the algorithm for computing the locations of grid lines;
1226 along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
1227 distinguished by the "horizontal" flag which is true for the horizontal axis and false
1228 for the vertical one.
1229 */
Philip Milnef6679c82011-09-18 11:36:57 -07001230 final class Axis {
Philip Milne48b55242011-06-29 11:09:45 -07001231 private static final int NEW = 0;
Philip Milne3f8956d2011-05-13 17:29:00 +01001232 private static final int PENDING = 1;
1233 private static final int COMPLETE = 2;
1234
1235 public final boolean horizontal;
1236
Philip Milnef6679c82011-09-18 11:36:57 -07001237 public int definedCount = UNDEFINED;
Philip Milne4a145d72011-09-29 14:14:25 -07001238 private int maxIndex = UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001239
Philip Milne93cd6a62011-07-12 14:49:45 -07001240 PackedMap<Spec, Bounds> groupBounds;
Philip Milne3f8956d2011-05-13 17:29:00 +01001241 public boolean groupBoundsValid = false;
1242
Philip Milne48b55242011-06-29 11:09:45 -07001243 PackedMap<Interval, MutableInt> forwardLinks;
1244 public boolean forwardLinksValid = false;
1245
1246 PackedMap<Interval, MutableInt> backwardLinks;
1247 public boolean backwardLinksValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001248
Philip Milne3f8956d2011-05-13 17:29:00 +01001249 public int[] leadingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001250 public boolean leadingMarginsValid = false;
1251
Philip Milne3f8956d2011-05-13 17:29:00 +01001252 public int[] trailingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001253 public boolean trailingMarginsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001254
1255 public Arc[] arcs;
1256 public boolean arcsValid = false;
1257
Philip Milneaa616f32011-05-27 18:38:01 -07001258 public int[] locations;
Philip Milne48b55242011-06-29 11:09:45 -07001259 public boolean locationsValid = false;
Philip Milneaa616f32011-05-27 18:38:01 -07001260
Philip Milne95054802014-05-23 19:50:40 -07001261 public boolean hasWeights;
1262 public boolean hasWeightsValid = false;
Philip Milne95054802014-05-23 19:50:40 -07001263 public int[] deltas;
1264
Philip Milnef6679c82011-09-18 11:36:57 -07001265 boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001266
Philip Milne48b55242011-06-29 11:09:45 -07001267 private MutableInt parentMin = new MutableInt(0);
1268 private MutableInt parentMax = new MutableInt(-MAX_SIZE);
1269
Philip Milne3f8956d2011-05-13 17:29:00 +01001270 private Axis(boolean horizontal) {
1271 this.horizontal = horizontal;
1272 }
1273
Philip Milne4a145d72011-09-29 14:14:25 -07001274 private int calculateMaxIndex() {
1275 // the number Integer.MIN_VALUE + 1 comes up in undefined cells
1276 int result = -1;
Philip Milneb3a8c542011-06-20 16:02:59 -07001277 for (int i = 0, N = getChildCount(); i < N; i++) {
1278 View c = getChildAt(i);
Philip Milneb3a8c542011-06-20 16:02:59 -07001279 LayoutParams params = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001280 Spec spec = horizontal ? params.columnSpec : params.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -07001281 Interval span = spec.span;
1282 result = max(result, span.min);
1283 result = max(result, span.max);
Philip Milne0f57cea2012-05-12 09:34:25 -07001284 result = max(result, span.size());
Philip Milne3f8956d2011-05-13 17:29:00 +01001285 }
Philip Milne4a145d72011-09-29 14:14:25 -07001286 return result == -1 ? UNDEFINED : result;
Philip Milne3f8956d2011-05-13 17:29:00 +01001287 }
1288
Philip Milne4a145d72011-09-29 14:14:25 -07001289 private int getMaxIndex() {
1290 if (maxIndex == UNDEFINED) {
1291 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children
Philip Milne3f8956d2011-05-13 17:29:00 +01001292 }
Philip Milne4a145d72011-09-29 14:14:25 -07001293 return maxIndex;
Philip Milnef6679c82011-09-18 11:36:57 -07001294 }
1295
1296 public int getCount() {
Philip Milne4a145d72011-09-29 14:14:25 -07001297 return max(definedCount, getMaxIndex());
Philip Milne3f8956d2011-05-13 17:29:00 +01001298 }
1299
1300 public void setCount(int count) {
Philip Milne0f57cea2012-05-12 09:34:25 -07001301 if (count != UNDEFINED && count < getMaxIndex()) {
1302 handleInvalidParams((horizontal ? "column" : "row") +
1303 "Count must be greater than or equal to the maximum of all grid indices " +
1304 "(and spans) defined in the LayoutParams of each child");
1305 }
Philip Milnef6679c82011-09-18 11:36:57 -07001306 this.definedCount = count;
Philip Milne3f8956d2011-05-13 17:29:00 +01001307 }
1308
1309 public boolean isOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -07001310 return orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001311 }
1312
1313 public void setOrderPreserved(boolean orderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -07001314 this.orderPreserved = orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001315 invalidateStructure();
1316 }
1317
Philip Milne93cd6a62011-07-12 14:49:45 -07001318 private PackedMap<Spec, Bounds> createGroupBounds() {
1319 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class);
Philip Milne48b55242011-06-29 11:09:45 -07001320 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001321 View c = getChildAt(i);
Philip Milnea8416442013-02-25 10:49:39 -08001322 // we must include views that are GONE here, see introductory javadoc
Philip Milne5125e212011-07-21 11:39:37 -07001323 LayoutParams lp = getLayoutParams(c);
1324 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Yigit Boyar6dafd872015-03-05 13:59:56 -08001325 Bounds bounds = spec.getAbsoluteAlignment(horizontal).getBounds();
Philip Milne5125e212011-07-21 11:39:37 -07001326 assoc.put(spec, bounds);
Philip Milne3f8956d2011-05-13 17:29:00 +01001327 }
Philip Milne48b55242011-06-29 11:09:45 -07001328 return assoc.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001329 }
1330
1331 private void computeGroupBounds() {
Philip Milneb3a8c542011-06-20 16:02:59 -07001332 Bounds[] values = groupBounds.values;
1333 for (int i = 0; i < values.length; i++) {
1334 values[i].reset();
Philip Milne3f8956d2011-05-13 17:29:00 +01001335 }
Philip Milneaa616f32011-05-27 18:38:01 -07001336 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001337 View c = getChildAt(i);
Philip Milnea8416442013-02-25 10:49:39 -08001338 // we must include views that are GONE here, see introductory javadoc
Philip Milne3f8956d2011-05-13 17:29:00 +01001339 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001340 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Yigit Boyarfbd99972015-03-05 14:17:19 -08001341 int size = getMeasurementIncludingMargin(c, horizontal) +
1342 ((spec.weight == 0) ? 0 : getDeltas()[i]);
Philip Milne95054802014-05-23 19:50:40 -07001343 groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
Philip Milne3f8956d2011-05-13 17:29:00 +01001344 }
1345 }
1346
Philip Milnef6679c82011-09-18 11:36:57 -07001347 public PackedMap<Spec, Bounds> getGroupBounds() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001348 if (groupBounds == null) {
1349 groupBounds = createGroupBounds();
1350 }
1351 if (!groupBoundsValid) {
1352 computeGroupBounds();
1353 groupBoundsValid = true;
1354 }
1355 return groupBounds;
1356 }
1357
1358 // Add values computed by alignment - taking the max of all alignments in each span
Philip Milne48b55242011-06-29 11:09:45 -07001359 private PackedMap<Interval, MutableInt> createLinks(boolean min) {
1360 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class);
Philip Milne93cd6a62011-07-12 14:49:45 -07001361 Spec[] keys = getGroupBounds().keys;
Philip Milne48b55242011-06-29 11:09:45 -07001362 for (int i = 0, N = keys.length; i < N; i++) {
1363 Interval span = min ? keys[i].span : keys[i].span.inverse();
1364 result.put(span, new MutableInt());
Philip Milne3f8956d2011-05-13 17:29:00 +01001365 }
Philip Milne48b55242011-06-29 11:09:45 -07001366 return result.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001367 }
1368
Philip Milne48b55242011-06-29 11:09:45 -07001369 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) {
1370 MutableInt[] spans = links.values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001371 for (int i = 0; i < spans.length; i++) {
1372 spans[i].reset();
1373 }
1374
Philip Milne5d1a9842011-07-07 11:47:08 -07001375 // Use getter to trigger a re-evaluation
Philip Milne48b55242011-06-29 11:09:45 -07001376 Bounds[] bounds = getGroupBounds().values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001377 for (int i = 0; i < bounds.length; i++) {
Philip Milne48b55242011-06-29 11:09:45 -07001378 int size = bounds[i].size(min);
Philip Milne48b55242011-06-29 11:09:45 -07001379 MutableInt valueHolder = links.getValue(i);
Philip Milne5125e212011-07-21 11:39:37 -07001380 // this effectively takes the max() of the minima and the min() of the maxima
1381 valueHolder.value = max(valueHolder.value, min ? size : -size);
Philip Milne3f8956d2011-05-13 17:29:00 +01001382 }
1383 }
1384
Philip Milne48b55242011-06-29 11:09:45 -07001385 private PackedMap<Interval, MutableInt> getForwardLinks() {
1386 if (forwardLinks == null) {
1387 forwardLinks = createLinks(true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001388 }
Philip Milne48b55242011-06-29 11:09:45 -07001389 if (!forwardLinksValid) {
1390 computeLinks(forwardLinks, true);
1391 forwardLinksValid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01001392 }
Philip Milne48b55242011-06-29 11:09:45 -07001393 return forwardLinks;
Philip Milne3f8956d2011-05-13 17:29:00 +01001394 }
1395
Philip Milne48b55242011-06-29 11:09:45 -07001396 private PackedMap<Interval, MutableInt> getBackwardLinks() {
1397 if (backwardLinks == null) {
1398 backwardLinks = createLinks(false);
1399 }
1400 if (!backwardLinksValid) {
1401 computeLinks(backwardLinks, false);
1402 backwardLinksValid = true;
1403 }
1404 return backwardLinks;
1405 }
1406
1407 private void include(List<Arc> arcs, Interval key, MutableInt size,
Philip Milneedd69512012-03-14 17:21:33 -07001408 boolean ignoreIfAlreadyPresent) {
Philip Milne48b55242011-06-29 11:09:45 -07001409 /*
1410 Remove self referential links.
1411 These appear:
1412 . as parental constraints when GridLayout has no children
1413 . when components have been marked as GONE
1414 */
1415 if (key.size() == 0) {
1416 return;
1417 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001418 // this bit below should really be computed outside here -
Philip Milne48b55242011-06-29 11:09:45 -07001419 // its just to stop default (row/col > 0) constraints obliterating valid entries
1420 if (ignoreIfAlreadyPresent) {
1421 for (Arc arc : arcs) {
1422 Interval span = arc.span;
1423 if (span.equals(key)) {
1424 return;
1425 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001426 }
1427 }
1428 arcs.add(new Arc(key, size));
1429 }
1430
Philip Milne48b55242011-06-29 11:09:45 -07001431 private void include(List<Arc> arcs, Interval key, MutableInt size) {
1432 include(arcs, key, size, true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001433 }
1434
Philip Milneaa616f32011-05-27 18:38:01 -07001435 // Group arcs by their first vertex, returning an array of arrays.
Philip Milne3f8956d2011-05-13 17:29:00 +01001436 // This is linear in the number of arcs.
Philip Milnef6679c82011-09-18 11:36:57 -07001437 Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
Philip Milne48b55242011-06-29 11:09:45 -07001438 int N = getCount() + 1; // the number of vertices
Philip Milne3f8956d2011-05-13 17:29:00 +01001439 Arc[][] result = new Arc[N][];
1440 int[] sizes = new int[N];
1441 for (Arc arc : arcs) {
1442 sizes[arc.span.min]++;
Philip Milne5d1a9842011-07-07 11:47:08 -07001443 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001444 for (int i = 0; i < sizes.length; i++) {
1445 result[i] = new Arc[sizes[i]];
1446 }
1447 // reuse the sizes array to hold the current last elements as we insert each arc
1448 Arrays.fill(sizes, 0);
1449 for (Arc arc : arcs) {
1450 int i = arc.span.min;
1451 result[i][sizes[i]++] = arc;
1452 }
1453
1454 return result;
1455 }
1456
Philip Milne48b55242011-06-29 11:09:45 -07001457 private Arc[] topologicalSort(final Arc[] arcs) {
1458 return new Object() {
1459 Arc[] result = new Arc[arcs.length];
1460 int cursor = result.length - 1;
1461 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001462 int[] visited = new int[getCount() + 1];
1463
Philip Milne48b55242011-06-29 11:09:45 -07001464 void walk(int loc) {
1465 switch (visited[loc]) {
1466 case NEW: {
1467 visited[loc] = PENDING;
1468 for (Arc arc : arcsByVertex[loc]) {
1469 walk(arc.span.max);
1470 result[cursor--] = arc;
Philip Milne3f8956d2011-05-13 17:29:00 +01001471 }
Philip Milne48b55242011-06-29 11:09:45 -07001472 visited[loc] = COMPLETE;
1473 break;
Philip Milne3f8956d2011-05-13 17:29:00 +01001474 }
Philip Milne48b55242011-06-29 11:09:45 -07001475 case PENDING: {
Philip Milneb65408f2012-05-21 10:44:46 -07001476 // le singe est dans l'arbre
Philip Milne48b55242011-06-29 11:09:45 -07001477 assert false;
1478 break;
1479 }
1480 case COMPLETE: {
1481 break;
1482 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001483 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001484 }
Philip Milne48b55242011-06-29 11:09:45 -07001485
1486 Arc[] sort() {
1487 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) {
1488 walk(loc);
1489 }
1490 assert cursor == -1;
1491 return result;
1492 }
1493 }.sort();
1494 }
1495
1496 private Arc[] topologicalSort(List<Arc> arcs) {
1497 return topologicalSort(arcs.toArray(new Arc[arcs.size()]));
Philip Milne3f8956d2011-05-13 17:29:00 +01001498 }
1499
Philip Milne48b55242011-06-29 11:09:45 -07001500 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) {
1501 for (int i = 0; i < links.keys.length; i++) {
1502 Interval key = links.keys[i];
1503 include(result, key, links.values[i], false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001504 }
Philip Milne48b55242011-06-29 11:09:45 -07001505 }
1506
1507 private Arc[] createArcs() {
1508 List<Arc> mins = new ArrayList<Arc>();
1509 List<Arc> maxs = new ArrayList<Arc>();
1510
1511 // Add the minimum values from the components.
1512 addComponentSizes(mins, getForwardLinks());
1513 // Add the maximum values from the components.
1514 addComponentSizes(maxs, getBackwardLinks());
Philip Milne3f8956d2011-05-13 17:29:00 +01001515
Philip Milne48b55242011-06-29 11:09:45 -07001516 // Add ordering constraints to prevent row/col sizes from going negative
Philip Milnef6679c82011-09-18 11:36:57 -07001517 if (orderPreserved) {
Philip Milne48b55242011-06-29 11:09:45 -07001518 // Add a constraint for every row/col
Philip Milne3f8956d2011-05-13 17:29:00 +01001519 for (int i = 0; i < getCount(); i++) {
Philip Milne899d5922011-07-21 11:39:37 -07001520 include(mins, new Interval(i, i + 1), new MutableInt(0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001521 }
1522 }
Philip Milne48b55242011-06-29 11:09:45 -07001523
1524 // Add the container constraints. Use the version of include that allows
1525 // duplicate entries in case a child spans the entire grid.
1526 int N = getCount();
1527 include(mins, new Interval(0, N), parentMin, false);
1528 include(maxs, new Interval(N, 0), parentMax, false);
1529
1530 // Sort
1531 Arc[] sMins = topologicalSort(mins);
1532 Arc[] sMaxs = topologicalSort(maxs);
1533
1534 return append(sMins, sMaxs);
1535 }
1536
1537 private void computeArcs() {
1538 // getting the links validates the values that are shared by the arc list
1539 getForwardLinks();
1540 getBackwardLinks();
Philip Milne3f8956d2011-05-13 17:29:00 +01001541 }
1542
Philip Milneaa616f32011-05-27 18:38:01 -07001543 public Arc[] getArcs() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001544 if (arcs == null) {
Philip Milneaa616f32011-05-27 18:38:01 -07001545 arcs = createArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001546 }
1547 if (!arcsValid) {
Philip Milne48b55242011-06-29 11:09:45 -07001548 computeArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001549 arcsValid = true;
1550 }
1551 return arcs;
1552 }
1553
Philip Milneaa616f32011-05-27 18:38:01 -07001554 private boolean relax(int[] locations, Arc entry) {
Philip Milne48b55242011-06-29 11:09:45 -07001555 if (!entry.valid) {
1556 return false;
1557 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001558 Interval span = entry.span;
1559 int u = span.min;
1560 int v = span.max;
1561 int value = entry.value.value;
1562 int candidate = locations[u] + value;
Philip Milneaa616f32011-05-27 18:38:01 -07001563 if (candidate > locations[v]) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001564 locations[v] = candidate;
1565 return true;
1566 }
1567 return false;
1568 }
1569
Philip Milnef6679c82011-09-18 11:36:57 -07001570 private void init(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001571 Arrays.fill(locations, 0);
Philip Milnef6679c82011-09-18 11:36:57 -07001572 }
1573
1574 private String arcsToString(List<Arc> arcs) {
Philip Milne4a145d72011-09-29 14:14:25 -07001575 String var = horizontal ? "x" : "y";
Philip Milnef6679c82011-09-18 11:36:57 -07001576 StringBuilder result = new StringBuilder();
Philip Milne4a145d72011-09-29 14:14:25 -07001577 boolean first = true;
1578 for (Arc arc : arcs) {
1579 if (first) {
1580 first = false;
Philip Milnef6679c82011-09-18 11:36:57 -07001581 } else {
Philip Milne4a145d72011-09-29 14:14:25 -07001582 result = result.append(", ");
Philip Milnef6679c82011-09-18 11:36:57 -07001583 }
1584 int src = arc.span.min;
1585 int dst = arc.span.max;
1586 int value = arc.value.value;
1587 result.append((src < dst) ?
Philip Milneedd69512012-03-14 17:21:33 -07001588 var + dst + "-" + var + src + ">=" + value :
1589 var + src + "-" + var + dst + "<=" + -value);
Philip Milnef6679c82011-09-18 11:36:57 -07001590
1591 }
1592 return result.toString();
1593 }
1594
1595 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) {
1596 List<Arc> culprits = new ArrayList<Arc>();
1597 List<Arc> removed = new ArrayList<Arc>();
1598 for (int c = 0; c < arcs.length; c++) {
1599 Arc arc = arcs[c];
1600 if (culprits0[c]) {
1601 culprits.add(arc);
1602 }
1603 if (!arc.valid) {
1604 removed.add(arc);
1605 }
1606 }
Adam Powell465ea742013-08-29 14:56:51 -07001607 mPrinter.println(axisName + " constraints: " + arcsToString(culprits) +
Philip Milne211d0332012-04-24 14:45:14 -07001608 " are inconsistent; permanently removing: " + arcsToString(removed) + ". ");
Philip Milnef6679c82011-09-18 11:36:57 -07001609 }
1610
Philip Milneaa616f32011-05-27 18:38:01 -07001611 /*
1612 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
1613
1614 GridLayout converts its requirements into a system of linear constraints of the
1615 form:
1616
1617 x[i] - x[j] < a[k]
1618
1619 Where the x[i] are variables and the a[k] are constants.
1620
1621 For example, if the variables were instead labeled x, y, z we might have:
1622
1623 x - y < 17
1624 y - z < 23
1625 z - x < 42
1626
1627 This is a special case of the Linear Programming problem that is, in turn,
1628 equivalent to the single-source shortest paths problem on a digraph, for
1629 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
Philip Milneaa616f32011-05-27 18:38:01 -07001630 */
Yigit Boyar98d5f042014-11-18 15:47:56 -08001631 private boolean solve(Arc[] arcs, int[] locations) {
1632 return solve(arcs, locations, true);
1633 }
1634
1635 private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) {
Philip Milnef6679c82011-09-18 11:36:57 -07001636 String axisName = horizontal ? "horizontal" : "vertical";
Philip Milne3f8956d2011-05-13 17:29:00 +01001637 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
Philip Milnef6679c82011-09-18 11:36:57 -07001638 boolean[] originalCulprits = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001639
Philip Milnef6679c82011-09-18 11:36:57 -07001640 for (int p = 0; p < arcs.length; p++) {
1641 init(locations);
1642
1643 // We take one extra pass over traditional Bellman-Ford (and omit their final step)
1644 for (int i = 0; i < N; i++) {
1645 boolean changed = false;
1646 for (int j = 0, length = arcs.length; j < length; j++) {
1647 changed |= relax(locations, arcs[j]);
Philip Milne3f8956d2011-05-13 17:29:00 +01001648 }
Philip Milnef6679c82011-09-18 11:36:57 -07001649 if (!changed) {
1650 if (originalCulprits != null) {
1651 logError(axisName, arcs, originalCulprits);
1652 }
Yigit Boyar98d5f042014-11-18 15:47:56 -08001653 return true;
Philip Milne48b55242011-06-29 11:09:45 -07001654 }
Philip Milnef6679c82011-09-18 11:36:57 -07001655 }
1656
Yigit Boyar98d5f042014-11-18 15:47:56 -08001657 if (!modifyOnError) {
1658 return false; // cannot solve with these constraints
1659 }
1660
Philip Milnef6679c82011-09-18 11:36:57 -07001661 boolean[] culprits = new boolean[arcs.length];
1662 for (int i = 0; i < N; i++) {
1663 for (int j = 0, length = arcs.length; j < length; j++) {
1664 culprits[j] |= relax(locations, arcs[j]);
1665 }
1666 }
1667
1668 if (p == 0) {
1669 originalCulprits = culprits;
1670 }
1671
1672 for (int i = 0; i < arcs.length; i++) {
1673 if (culprits[i]) {
1674 Arc arc = arcs[i];
1675 // Only remove max values, min values alone cannot be inconsistent
1676 if (arc.span.min < arc.span.max) {
1677 continue;
1678 }
1679 arc.valid = false;
1680 break;
1681 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001682 }
1683 }
Yigit Boyar98d5f042014-11-18 15:47:56 -08001684 return true;
Philip Milne3f8956d2011-05-13 17:29:00 +01001685 }
1686
Philip Milneaa616f32011-05-27 18:38:01 -07001687 private void computeMargins(boolean leading) {
1688 int[] margins = leading ? leadingMargins : trailingMargins;
Philip Milneb3a8c542011-06-20 16:02:59 -07001689 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001690 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001691 if (c.getVisibility() == View.GONE) continue;
Philip Milne3f8956d2011-05-13 17:29:00 +01001692 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001693 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1694 Interval span = spec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001695 int index = leading ? span.min : span.max;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001696 margins[index] = max(margins[index], getMargin1(c, horizontal, leading));
Philip Milne3f8956d2011-05-13 17:29:00 +01001697 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001698 }
1699
Philip Milnef6679c82011-09-18 11:36:57 -07001700 // External entry points
1701
1702 public int[] getLeadingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001703 if (leadingMargins == null) {
1704 leadingMargins = new int[getCount() + 1];
1705 }
1706 if (!leadingMarginsValid) {
1707 computeMargins(true);
1708 leadingMarginsValid = true;
1709 }
1710 return leadingMargins;
1711 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001712
Philip Milnef6679c82011-09-18 11:36:57 -07001713 public int[] getTrailingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001714 if (trailingMargins == null) {
1715 trailingMargins = new int[getCount() + 1];
1716 }
1717 if (!trailingMarginsValid) {
1718 computeMargins(false);
1719 trailingMarginsValid = true;
1720 }
1721 return trailingMargins;
1722 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001723
Yigit Boyar98d5f042014-11-18 15:47:56 -08001724 private boolean solve(int[] a) {
1725 return solve(getArcs(), a);
Philip Milne87260842014-05-22 13:56:03 -07001726 }
1727
Philip Milne95054802014-05-23 19:50:40 -07001728 private boolean computeHasWeights() {
1729 for (int i = 0, N = getChildCount(); i < N; i++) {
Yigit Boyar6dafd872015-03-05 13:59:56 -08001730 final View child = getChildAt(i);
1731 if (child.getVisibility() == View.GONE) {
1732 continue;
1733 }
1734 LayoutParams lp = getLayoutParams(child);
Philip Milne95054802014-05-23 19:50:40 -07001735 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1736 if (spec.weight != 0) {
Philip Milne87260842014-05-22 13:56:03 -07001737 return true;
1738 }
1739 }
1740 return false;
1741 }
1742
Philip Milne95054802014-05-23 19:50:40 -07001743 private boolean hasWeights() {
1744 if (!hasWeightsValid) {
1745 hasWeights = computeHasWeights();
1746 hasWeightsValid = true;
1747 }
1748 return hasWeights;
1749 }
1750
Philip Milne95054802014-05-23 19:50:40 -07001751 public int[] getDeltas() {
1752 if (deltas == null) {
1753 deltas = new int[getChildCount()];
1754 }
1755 return deltas;
1756 }
1757
Yigit Boyar98d5f042014-11-18 15:47:56 -08001758 private void shareOutDelta(int totalDelta, float totalWeight) {
1759 Arrays.fill(deltas, 0);
Philip Milne95054802014-05-23 19:50:40 -07001760 for (int i = 0, N = getChildCount(); i < N; i++) {
Yigit Boyar6dafd872015-03-05 13:59:56 -08001761 final View c = getChildAt(i);
1762 if (c.getVisibility() == View.GONE) {
1763 continue;
1764 }
Philip Milne95054802014-05-23 19:50:40 -07001765 LayoutParams lp = getLayoutParams(c);
1766 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1767 float weight = spec.weight;
1768 if (weight != 0) {
Philip Milne87260842014-05-22 13:56:03 -07001769 int delta = Math.round((weight * totalDelta / totalWeight));
Philip Milne95054802014-05-23 19:50:40 -07001770 deltas[i] = delta;
Yigit Boyar98d5f042014-11-18 15:47:56 -08001771 // the two adjustments below are to counter the above rounding and avoid
1772 // off-by-ones at the end
Philip Milne87260842014-05-22 13:56:03 -07001773 totalDelta -= delta;
1774 totalWeight -= weight;
1775 }
1776 }
1777 }
1778
1779 private void solveAndDistributeSpace(int[] a) {
Philip Milne95054802014-05-23 19:50:40 -07001780 Arrays.fill(getDeltas(), 0);
Philip Milne87260842014-05-22 13:56:03 -07001781 solve(a);
Yigit Boyar98d5f042014-11-18 15:47:56 -08001782 int deltaMax = parentMin.value * getChildCount() + 1; //exclusive
1783 if (deltaMax < 2) {
1784 return; //don't have any delta to distribute
1785 }
1786 int deltaMin = 0; //inclusive
1787
1788 float totalWeight = calculateTotalWeight();
1789
1790 int validDelta = -1; //delta for which a solution exists
1791 boolean validSolution = true;
1792 // do a binary search to find the max delta that won't conflict with constraints
1793 while(deltaMin < deltaMax) {
Deepanshu Gupta7a8fb262016-01-07 13:29:04 -08001794 // cast to long to prevent overflow.
1795 final int delta = (int) (((long) deltaMin + deltaMax) / 2);
Yigit Boyar98d5f042014-11-18 15:47:56 -08001796 invalidateValues();
1797 shareOutDelta(delta, totalWeight);
1798 validSolution = solve(getArcs(), a, false);
1799 if (validSolution) {
1800 validDelta = delta;
1801 deltaMin = delta + 1;
1802 } else {
1803 deltaMax = delta;
1804 }
1805 }
1806 if (validDelta > 0 && !validSolution) {
1807 // last solution was not successful but we have a successful one. Use it.
1808 invalidateValues();
1809 shareOutDelta(validDelta, totalWeight);
1810 solve(a);
1811 }
1812 }
1813
1814 private float calculateTotalWeight() {
1815 float totalWeight = 0f;
1816 for (int i = 0, N = getChildCount(); i < N; i++) {
1817 View c = getChildAt(i);
Yigit Boyar6dafd872015-03-05 13:59:56 -08001818 if (c.getVisibility() == View.GONE) {
1819 continue;
1820 }
Yigit Boyar98d5f042014-11-18 15:47:56 -08001821 LayoutParams lp = getLayoutParams(c);
1822 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1823 totalWeight += spec.weight;
1824 }
1825 return totalWeight;
Philip Milne87260842014-05-22 13:56:03 -07001826 }
1827
1828 private void computeLocations(int[] a) {
Philip Milne95054802014-05-23 19:50:40 -07001829 if (!hasWeights()) {
1830 solve(a);
1831 } else {
1832 solveAndDistributeSpace(a);
1833 }
Philip Milne4a145d72011-09-29 14:14:25 -07001834 if (!orderPreserved) {
1835 // Solve returns the smallest solution to the constraint system for which all
1836 // values are positive. One value is therefore zero - though if the row/col
1837 // order is not preserved this may not be the first vertex. For consistency,
1838 // translate all the values so that they measure the distance from a[0]; the
1839 // leading edge of the parent. After this transformation some values may be
1840 // negative.
1841 int a0 = a[0];
1842 for (int i = 0, N = a.length; i < N; i++) {
1843 a[i] = a[i] - a0;
1844 }
1845 }
Philip Milneaa616f32011-05-27 18:38:01 -07001846 }
1847
Philip Milnef6679c82011-09-18 11:36:57 -07001848 public int[] getLocations() {
Philip Milneaa616f32011-05-27 18:38:01 -07001849 if (locations == null) {
1850 int N = getCount() + 1;
1851 locations = new int[N];
1852 }
Philip Milne48b55242011-06-29 11:09:45 -07001853 if (!locationsValid) {
1854 computeLocations(locations);
1855 locationsValid = true;
1856 }
Philip Milneaa616f32011-05-27 18:38:01 -07001857 return locations;
1858 }
1859
Philip Milne3f8956d2011-05-13 17:29:00 +01001860 private int size(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001861 // The parental edges are attached to vertices 0 and N - even when order is not
1862 // being preserved and other vertices fall outside this range. Measure the distance
1863 // between vertices 0 and N, assuming that locations[0] = 0.
1864 return locations[getCount()];
Philip Milne3f8956d2011-05-13 17:29:00 +01001865 }
1866
Philip Milne48b55242011-06-29 11:09:45 -07001867 private void setParentConstraints(int min, int max) {
1868 parentMin.value = min;
1869 parentMax.value = -max;
1870 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001871 }
1872
Philip Milne48b55242011-06-29 11:09:45 -07001873 private int getMeasure(int min, int max) {
1874 setParentConstraints(min, max);
1875 return size(getLocations());
1876 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001877
Philip Milnef6679c82011-09-18 11:36:57 -07001878 public int getMeasure(int measureSpec) {
Philip Milne48b55242011-06-29 11:09:45 -07001879 int mode = MeasureSpec.getMode(measureSpec);
1880 int size = MeasureSpec.getSize(measureSpec);
1881 switch (mode) {
1882 case MeasureSpec.UNSPECIFIED: {
Philip Milne93cd6a62011-07-12 14:49:45 -07001883 return getMeasure(0, MAX_SIZE);
Philip Milne48b55242011-06-29 11:09:45 -07001884 }
1885 case MeasureSpec.EXACTLY: {
1886 return getMeasure(size, size);
1887 }
1888 case MeasureSpec.AT_MOST: {
1889 return getMeasure(0, size);
1890 }
1891 default: {
1892 assert false;
1893 return 0;
1894 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001895 }
Philip Milne48b55242011-06-29 11:09:45 -07001896 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001897
Philip Milnef6679c82011-09-18 11:36:57 -07001898 public void layout(int size) {
Philip Milne48b55242011-06-29 11:09:45 -07001899 setParentConstraints(size, size);
1900 getLocations();
Philip Milne3f8956d2011-05-13 17:29:00 +01001901 }
1902
Philip Milnef6679c82011-09-18 11:36:57 -07001903 public void invalidateStructure() {
Philip Milne4a145d72011-09-29 14:14:25 -07001904 maxIndex = UNDEFINED;
Philip Milneaa616f32011-05-27 18:38:01 -07001905
Philip Milne3f8956d2011-05-13 17:29:00 +01001906 groupBounds = null;
Philip Milne48b55242011-06-29 11:09:45 -07001907 forwardLinks = null;
1908 backwardLinks = null;
1909
Philip Milneaa616f32011-05-27 18:38:01 -07001910 leadingMargins = null;
1911 trailingMargins = null;
Philip Milnec9885f62011-06-15 17:07:35 -07001912 arcs = null;
Philip Milne48b55242011-06-29 11:09:45 -07001913
Philip Milneaa616f32011-05-27 18:38:01 -07001914 locations = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001915
Philip Milne95054802014-05-23 19:50:40 -07001916 deltas = null;
1917 hasWeightsValid = false;
1918
Philip Milne3f8956d2011-05-13 17:29:00 +01001919 invalidateValues();
1920 }
1921
Philip Milnef6679c82011-09-18 11:36:57 -07001922 public void invalidateValues() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001923 groupBoundsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001924 forwardLinksValid = false;
1925 backwardLinksValid = false;
1926
Philip Milneaa616f32011-05-27 18:38:01 -07001927 leadingMarginsValid = false;
1928 trailingMarginsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001929 arcsValid = false;
1930
1931 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001932 }
1933 }
1934
1935 /**
1936 * Layout information associated with each of the children of a GridLayout.
1937 * <p>
1938 * GridLayout supports both row and column spanning and arbitrary forms of alignment within
1939 * each cell group. The fundamental parameters associated with each cell group are
1940 * gathered into their vertical and horizontal components and stored
Philip Milne93cd6a62011-07-12 14:49:45 -07001941 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
Philip Milne6216e872012-02-16 17:15:50 -08001942 * {@link GridLayout.Spec Specs} are immutable structures
Philip Milneb0ce49b2011-07-15 15:39:07 -07001943 * and may be shared between the layout parameters of different children.
Philip Milne3f8956d2011-05-13 17:29:00 +01001944 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001945 * The row and column specs contain the leading and trailing indices along each axis
Philip Milneaa616f32011-05-27 18:38:01 -07001946 * and together specify the four grid indices that delimit the cells of this cell group.
Philip Milne3f8956d2011-05-13 17:29:00 +01001947 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001948 * The alignment properties of the row and column specs together specify
Philip Milne3f8956d2011-05-13 17:29:00 +01001949 * both aspects of alignment within the cell group. It is also possible to specify a child's
1950 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
1951 * method.
Philip Milne87260842014-05-22 13:56:03 -07001952 * <p>
1953 * The weight property is also included in Spec and specifies the proportion of any
Philip Milne95054802014-05-23 19:50:40 -07001954 * excess space that is due to the associated view.
Philip Milnef6679c82011-09-18 11:36:57 -07001955 *
1956 * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
1957 *
1958 * Because the default values of the {@link #width} and {@link #height}
1959 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
1960 * declared in the layout parameters of GridLayout's children. In addition,
1961 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
1962 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
1963 * instead controlled by the principle of <em>flexibility</em>,
1964 * as discussed in {@link GridLayout}.
1965 *
1966 * <h4>Summary</h4>
1967 *
1968 * You should not need to use either of the special size values:
1969 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
1970 * a GridLayout.
Philip Milne3f8956d2011-05-13 17:29:00 +01001971 *
1972 * <h4>Default values</h4>
1973 *
1974 * <ul>
1975 * <li>{@link #width} = {@link #WRAP_CONTENT}</li>
1976 * <li>{@link #height} = {@link #WRAP_CONTENT}</li>
1977 * <li>{@link #topMargin} = 0 when
1978 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001979 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001980 * indicate that a default value should be computed on demand. </li>
1981 * <li>{@link #leftMargin} = 0 when
1982 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001983 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001984 * indicate that a default value should be computed on demand. </li>
1985 * <li>{@link #bottomMargin} = 0 when
1986 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001987 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001988 * indicate that a default value should be computed on demand. </li>
1989 * <li>{@link #rightMargin} = 0 when
1990 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001991 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001992 * indicate that a default value should be computed on demand. </li>
Philip Milnef6679c82011-09-18 11:36:57 -07001993 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
1994 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
1995 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
Philip Milne87260842014-05-22 13:56:03 -07001996 * <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
Philip Milnef6679c82011-09-18 11:36:57 -07001997 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
1998 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
Philip Milne6216e872012-02-16 17:15:50 -08001999 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
Philip Milne87260842014-05-22 13:56:03 -07002000 * <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002001 * </ul>
2002 *
Philip Milnef6679c82011-09-18 11:36:57 -07002003 * See {@link GridLayout} for a more complete description of the conventions
2004 * used by GridLayout in the interpretation of the properties of this class.
2005 *
Philip Milne3f8956d2011-05-13 17:29:00 +01002006 * @attr ref android.R.styleable#GridLayout_Layout_layout_row
2007 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
Philip Milne87260842014-05-22 13:56:03 -07002008 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
Philip Milne3f8956d2011-05-13 17:29:00 +01002009 * @attr ref android.R.styleable#GridLayout_Layout_layout_column
2010 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
Philip Milne87260842014-05-22 13:56:03 -07002011 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
Philip Milne3f8956d2011-05-13 17:29:00 +01002012 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
2013 */
2014 public static class LayoutParams extends MarginLayoutParams {
2015
2016 // Default values
2017
2018 private static final int DEFAULT_WIDTH = WRAP_CONTENT;
2019 private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
2020 private static final int DEFAULT_MARGIN = UNDEFINED;
2021 private static final int DEFAULT_ROW = UNDEFINED;
2022 private static final int DEFAULT_COLUMN = UNDEFINED;
Philip Milnef4748702011-06-09 18:30:32 -07002023 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01002024 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
Philip Milne3f8956d2011-05-13 17:29:00 +01002025
2026 // TypedArray indices
2027
Philip Milneb0ce49b2011-07-15 15:39:07 -07002028 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin;
2029 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft;
2030 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop;
2031 private static final int RIGHT_MARGIN =
2032 R.styleable.ViewGroup_MarginLayout_layout_marginRight;
Philip Milne3f8956d2011-05-13 17:29:00 +01002033 private static final int BOTTOM_MARGIN =
Philip Milneb0ce49b2011-07-15 15:39:07 -07002034 R.styleable.ViewGroup_MarginLayout_layout_marginBottom;
Philip Milneb0ce49b2011-07-15 15:39:07 -07002035 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
2036 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
Philip Milne87260842014-05-22 13:56:03 -07002037 private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
Philip Milne5d1a9842011-07-07 11:47:08 -07002038
Philip Milneb0ce49b2011-07-15 15:39:07 -07002039 private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
2040 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
Philip Milne87260842014-05-22 13:56:03 -07002041 private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
Philip Milne5d1a9842011-07-07 11:47:08 -07002042
Philip Milneb0ce49b2011-07-15 15:39:07 -07002043 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
Philip Milne3f8956d2011-05-13 17:29:00 +01002044
2045 // Instance variables
2046
2047 /**
Philip Milnef6679c82011-09-18 11:36:57 -07002048 * The spec that defines the vertical characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01002049 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08002050 * If an assignment is made to this field after a measurement or layout operation
2051 * has already taken place, a call to
2052 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
2053 * must be made to notify GridLayout of the change. GridLayout is normally able
2054 * to detect when code fails to observe this rule, issue a warning and take steps to
2055 * compensate for the omission. This facility is implemented on a best effort basis
2056 * and should not be relied upon in production code - so it is best to include the above
2057 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01002058 */
Philip Milnef6679c82011-09-18 11:36:57 -07002059 public Spec rowSpec = Spec.UNDEFINED;
2060
Philip Milne3f8956d2011-05-13 17:29:00 +01002061 /**
Philip Milnef6679c82011-09-18 11:36:57 -07002062 * The spec that defines the horizontal characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01002063 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08002064 * If an assignment is made to this field after a measurement or layout operation
2065 * has already taken place, a call to
2066 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
2067 * must be made to notify GridLayout of the change. GridLayout is normally able
2068 * to detect when code fails to observe this rule, issue a warning and take steps to
2069 * compensate for the omission. This facility is implemented on a best effort basis
2070 * and should not be relied upon in production code - so it is best to include the above
2071 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01002072 */
Philip Milnef6679c82011-09-18 11:36:57 -07002073 public Spec columnSpec = Spec.UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01002074
2075 // Constructors
2076
2077 private LayoutParams(
2078 int width, int height,
2079 int left, int top, int right, int bottom,
Philip Milne93cd6a62011-07-12 14:49:45 -07002080 Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002081 super(width, height);
2082 setMargins(left, top, right, bottom);
Philip Milne93cd6a62011-07-12 14:49:45 -07002083 this.rowSpec = rowSpec;
2084 this.columnSpec = columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01002085 }
2086
2087 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002088 * Constructs a new LayoutParams instance for this <code>rowSpec</code>
2089 * and <code>columnSpec</code>. All other fields are initialized with
Philip Milne3f8956d2011-05-13 17:29:00 +01002090 * default values as defined in {@link LayoutParams}.
2091 *
Philip Milne93cd6a62011-07-12 14:49:45 -07002092 * @param rowSpec the rowSpec
2093 * @param columnSpec the columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +01002094 */
Philip Milne93cd6a62011-07-12 14:49:45 -07002095 public LayoutParams(Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002096 this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
2097 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
Philip Milne93cd6a62011-07-12 14:49:45 -07002098 rowSpec, columnSpec);
Philip Milne3f8956d2011-05-13 17:29:00 +01002099 }
2100
2101 /**
2102 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
2103 */
2104 public LayoutParams() {
Philip Milnef6679c82011-09-18 11:36:57 -07002105 this(Spec.UNDEFINED, Spec.UNDEFINED);
Philip Milne3f8956d2011-05-13 17:29:00 +01002106 }
2107
2108 // Copying constructors
2109
2110 /**
2111 * {@inheritDoc}
2112 */
2113 public LayoutParams(ViewGroup.LayoutParams params) {
2114 super(params);
2115 }
2116
2117 /**
2118 * {@inheritDoc}
2119 */
2120 public LayoutParams(MarginLayoutParams params) {
2121 super(params);
2122 }
2123
2124 /**
Alan Viverette0a0e1552013-08-07 13:24:09 -07002125 * Copy constructor. Clones the width, height, margin values, row spec,
2126 * and column spec of the source.
2127 *
2128 * @param source The layout params to copy from.
Philip Milne3f8956d2011-05-13 17:29:00 +01002129 */
Alan Viverette0a0e1552013-08-07 13:24:09 -07002130 public LayoutParams(LayoutParams source) {
2131 super(source);
2132
2133 this.rowSpec = source.rowSpec;
2134 this.columnSpec = source.columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01002135 }
2136
2137 // AttributeSet constructors
2138
Philip Milne3f8956d2011-05-13 17:29:00 +01002139 /**
2140 * {@inheritDoc}
2141 *
2142 * Values not defined in the attribute set take the default values
2143 * defined in {@link LayoutParams}.
2144 */
2145 public LayoutParams(Context context, AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -07002146 super(context, attrs);
2147 reInitSuper(context, attrs);
2148 init(context, attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +01002149 }
2150
2151 // Implementation
2152
Philip Milne3f8956d2011-05-13 17:29:00 +01002153 // Reinitialise the margins using a different default policy than MarginLayoutParams.
2154 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
2155 // so that a layout manager default can be accessed post set up. We need this as, at the
2156 // point of installation, we do not know how many rows/cols there are and therefore
2157 // which elements are positioned next to the container's trailing edges. We need to
2158 // know this as margins around the container's boundary should have different
2159 // defaults to those between peers.
2160
2161 // This method could be parametrized and moved into MarginLayout.
2162 private void reInitSuper(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07002163 TypedArray a =
2164 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
Philip Milne3f8956d2011-05-13 17:29:00 +01002165 try {
2166 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
2167
2168 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
2169 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
2170 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
2171 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
2172 } finally {
2173 a.recycle();
2174 }
2175 }
2176
Philip Milne5125e212011-07-21 11:39:37 -07002177 private void init(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07002178 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
Philip Milne3f8956d2011-05-13 17:29:00 +01002179 try {
Philip Milne5125e212011-07-21 11:39:37 -07002180 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
Philip Milne3f8956d2011-05-13 17:29:00 +01002181
Philip Milne1e548252011-06-16 19:02:33 -07002182 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
Philip Milne5125e212011-07-21 11:39:37 -07002183 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne87260842014-05-22 13:56:03 -07002184 float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
2185 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
Philip Milne3f8956d2011-05-13 17:29:00 +01002186
Philip Milne1e548252011-06-16 19:02:33 -07002187 int row = a.getInt(ROW, DEFAULT_ROW);
2188 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne87260842014-05-22 13:56:03 -07002189 float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
2190 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
Philip Milne3f8956d2011-05-13 17:29:00 +01002191 } finally {
2192 a.recycle();
2193 }
2194 }
2195
2196 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002197 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
Philip Milne6216e872012-02-16 17:15:50 -08002198 * See {@link Gravity}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002199 *
Philip Milne7fd94872011-06-07 20:14:17 -07002200 * @param gravity the new gravity value
Philip Milne3f8956d2011-05-13 17:29:00 +01002201 *
2202 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
2203 */
2204 public void setGravity(int gravity) {
Philip Milne5125e212011-07-21 11:39:37 -07002205 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
2206 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01002207 }
2208
2209 @Override
2210 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
2211 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
2212 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
2213 }
2214
Philip Milnef6679c82011-09-18 11:36:57 -07002215 final void setRowSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002216 rowSpec = rowSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002217 }
2218
Philip Milnef6679c82011-09-18 11:36:57 -07002219 final void setColumnSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002220 columnSpec = columnSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002221 }
Philip Milned7dd8902012-01-26 16:55:30 -08002222
2223 @Override
2224 public boolean equals(Object o) {
2225 if (this == o) return true;
2226 if (o == null || getClass() != o.getClass()) return false;
2227
2228 LayoutParams that = (LayoutParams) o;
2229
2230 if (!columnSpec.equals(that.columnSpec)) return false;
2231 if (!rowSpec.equals(that.rowSpec)) return false;
2232
2233 return true;
2234 }
2235
2236 @Override
2237 public int hashCode() {
2238 int result = rowSpec.hashCode();
2239 result = 31 * result + columnSpec.hashCode();
2240 return result;
2241 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002242 }
2243
Philip Milneaa616f32011-05-27 18:38:01 -07002244 /*
2245 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
2246 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
2247 */
Philip Milnef6679c82011-09-18 11:36:57 -07002248 final static class Arc {
Philip Milne3f8956d2011-05-13 17:29:00 +01002249 public final Interval span;
Philip Milneaa616f32011-05-27 18:38:01 -07002250 public final MutableInt value;
Philip Milne48b55242011-06-29 11:09:45 -07002251 public boolean valid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01002252
Philip Milneaa616f32011-05-27 18:38:01 -07002253 public Arc(Interval span, MutableInt value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002254 this.span = span;
2255 this.value = value;
2256 }
2257
2258 @Override
2259 public String toString() {
Philip Milne48b55242011-06-29 11:09:45 -07002260 return span + " " + (!valid ? "+>" : "->") + " " + value;
Philip Milne3f8956d2011-05-13 17:29:00 +01002261 }
2262 }
2263
2264 // A mutable Integer - used to avoid heap allocation during the layout operation
2265
Philip Milnef6679c82011-09-18 11:36:57 -07002266 final static class MutableInt {
Philip Milne3f8956d2011-05-13 17:29:00 +01002267 public int value;
2268
Philip Milnef6679c82011-09-18 11:36:57 -07002269 public MutableInt() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002270 reset();
2271 }
2272
Philip Milnef6679c82011-09-18 11:36:57 -07002273 public MutableInt(int value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002274 this.value = value;
2275 }
2276
Philip Milnef6679c82011-09-18 11:36:57 -07002277 public void reset() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002278 value = Integer.MIN_VALUE;
2279 }
Philip Milne48b55242011-06-29 11:09:45 -07002280
2281 @Override
2282 public String toString() {
2283 return Integer.toString(value);
2284 }
2285 }
2286
Philip Milnef6679c82011-09-18 11:36:57 -07002287 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
Philip Milne48b55242011-06-29 11:09:45 -07002288 private final Class<K> keyType;
2289 private final Class<V> valueType;
2290
2291 private Assoc(Class<K> keyType, Class<V> valueType) {
2292 this.keyType = keyType;
2293 this.valueType = valueType;
2294 }
2295
Philip Milnef6679c82011-09-18 11:36:57 -07002296 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
Philip Milne48b55242011-06-29 11:09:45 -07002297 return new Assoc<K, V>(keyType, valueType);
2298 }
2299
2300 public void put(K key, V value) {
2301 add(Pair.create(key, value));
2302 }
2303
2304 @SuppressWarnings(value = "unchecked")
2305 public PackedMap<K, V> pack() {
2306 int N = size();
2307 K[] keys = (K[]) Array.newInstance(keyType, N);
2308 V[] values = (V[]) Array.newInstance(valueType, N);
2309 for (int i = 0; i < N; i++) {
2310 keys[i] = get(i).first;
2311 values[i] = get(i).second;
2312 }
2313 return new PackedMap<K, V>(keys, values);
2314 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002315 }
2316
Philip Milneaa616f32011-05-27 18:38:01 -07002317 /*
2318 This data structure is used in place of a Map where we have an index that refers to the order
2319 in which each key/value pairs were added to the map. In this case we store keys and values
2320 in arrays of a length that is equal to the number of unique keys. We also maintain an
2321 array of indexes from insertion order to the compacted arrays of keys and values.
2322
2323 Note that behavior differs from that of a LinkedHashMap in that repeated entries
2324 *do* get added multiples times. So the length of index is equals to the number of
2325 items added.
2326
2327 This is useful in the GridLayout class where we can rely on the order of children not
2328 changing during layout - to use integer-based lookup for our internal structures
2329 rather than using (and storing) an implementation of Map<Key, ?>.
2330 */
Philip Milne3f8956d2011-05-13 17:29:00 +01002331 @SuppressWarnings(value = "unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -07002332 final static class PackedMap<K, V> {
Philip Milne3f8956d2011-05-13 17:29:00 +01002333 public final int[] index;
2334 public final K[] keys;
2335 public final V[] values;
2336
2337 private PackedMap(K[] keys, V[] values) {
2338 this.index = createIndex(keys);
2339
Philip Milneaa616f32011-05-27 18:38:01 -07002340 this.keys = compact(keys, index);
2341 this.values = compact(values, index);
Philip Milne3f8956d2011-05-13 17:29:00 +01002342 }
2343
Philip Milnef6679c82011-09-18 11:36:57 -07002344 public V getValue(int i) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002345 return values[index[i]];
2346 }
2347
2348 private static <K> int[] createIndex(K[] keys) {
2349 int size = keys.length;
2350 int[] result = new int[size];
2351
2352 Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
2353 for (int i = 0; i < size; i++) {
2354 K key = keys[i];
2355 Integer index = keyToIndex.get(key);
2356 if (index == null) {
2357 index = keyToIndex.size();
2358 keyToIndex.put(key, index);
2359 }
2360 result[i] = index;
2361 }
2362 return result;
2363 }
2364
Philip Milneaa616f32011-05-27 18:38:01 -07002365 /*
2366 Create a compact array of keys or values using the supplied index.
2367 */
2368 private static <K> K[] compact(K[] a, int[] index) {
2369 int size = a.length;
2370 Class<?> componentType = a.getClass().getComponentType();
Philip Milne51f17d52011-06-13 10:44:49 -07002371 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01002372
2373 // this overwrite duplicates, retaining the last equivalent entry
2374 for (int i = 0; i < size; i++) {
Philip Milneaa616f32011-05-27 18:38:01 -07002375 result[index[i]] = a[i];
Philip Milne3f8956d2011-05-13 17:29:00 +01002376 }
2377 return result;
2378 }
2379 }
2380
Philip Milneaa616f32011-05-27 18:38:01 -07002381 /*
Philip Milne93cd6a62011-07-12 14:49:45 -07002382 For each group (with a given alignment) we need to store the amount of space required
Philip Milne7fd94872011-06-07 20:14:17 -07002383 before the alignment point and the amount of space required after it. One side of this
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002384 calculation is always 0 for START and END alignments but we don't make use of this.
Philip Milneaa616f32011-05-27 18:38:01 -07002385 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
2386 simple optimisations are possible.
2387
2388 The general algorithm therefore is to create a Map (actually a PackedMap) from
Philip Milne93cd6a62011-07-12 14:49:45 -07002389 group to Bounds and to loop through all Views in the group taking the maximum
Philip Milneaa616f32011-05-27 18:38:01 -07002390 of the values for each View.
2391 */
Philip Milnef6679c82011-09-18 11:36:57 -07002392 static class Bounds {
Philip Milne7fd94872011-06-07 20:14:17 -07002393 public int before;
2394 public int after;
Philip Milne5125e212011-07-21 11:39:37 -07002395 public int flexibility; // we're flexible iff all included specs are flexible
Philip Milne3f8956d2011-05-13 17:29:00 +01002396
2397 private Bounds() {
2398 reset();
2399 }
2400
Philip Milnea1f7b102011-06-23 11:10:13 -07002401 protected void reset() {
Philip Milne7fd94872011-06-07 20:14:17 -07002402 before = Integer.MIN_VALUE;
2403 after = Integer.MIN_VALUE;
Philip Milne5125e212011-07-21 11:39:37 -07002404 flexibility = CAN_STRETCH; // from the above, we're flexible when empty
Philip Milne3f8956d2011-05-13 17:29:00 +01002405 }
2406
Philip Milnea1f7b102011-06-23 11:10:13 -07002407 protected void include(int before, int after) {
Philip Milne7fd94872011-06-07 20:14:17 -07002408 this.before = max(this.before, before);
2409 this.after = max(this.after, after);
Philip Milne3f8956d2011-05-13 17:29:00 +01002410 }
2411
Philip Milne48b55242011-06-29 11:09:45 -07002412 protected int size(boolean min) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002413 if (!min) {
Philip Milne5125e212011-07-21 11:39:37 -07002414 if (canStretch(flexibility)) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002415 return MAX_SIZE;
2416 }
Philip Milne48b55242011-06-29 11:09:45 -07002417 }
Philip Milne4885f2f2014-05-23 23:12:11 -07002418 return before + after;
Philip Milne3f8956d2011-05-13 17:29:00 +01002419 }
2420
Philip Milne1557fd72012-04-04 23:41:34 -07002421 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
Philip Milne7a23b492012-04-24 22:12:36 -07002422 return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne1557fd72012-04-04 23:41:34 -07002423 }
2424
Philip Milne95054802014-05-23 19:50:40 -07002425 protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
Philip Milne5125e212011-07-21 11:39:37 -07002426 this.flexibility &= spec.getFlexibility();
Philip Milne1557fd72012-04-04 23:41:34 -07002427 boolean horizontal = axis.horizontal;
Yigit Boyar6dafd872015-03-05 13:59:56 -08002428 Alignment alignment = spec.getAbsoluteAlignment(axis.horizontal);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002429 // todo test this works correctly when the returned value is UNDEFINED
Philip Milne7a23b492012-04-24 22:12:36 -07002430 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne48b55242011-06-29 11:09:45 -07002431 include(before, size - before);
Philip Milnea1f7b102011-06-23 11:10:13 -07002432 }
2433
Philip Milne3f8956d2011-05-13 17:29:00 +01002434 @Override
2435 public String toString() {
2436 return "Bounds{" +
Philip Milne7fd94872011-06-07 20:14:17 -07002437 "before=" + before +
2438 ", after=" + after +
Philip Milne3f8956d2011-05-13 17:29:00 +01002439 '}';
2440 }
2441 }
2442
2443 /**
2444 * An Interval represents a contiguous range of values that lie between
2445 * the interval's {@link #min} and {@link #max} values.
2446 * <p>
2447 * Intervals are immutable so may be passed as values and used as keys in hash tables.
2448 * It is not necessary to have multiple instances of Intervals which have the same
2449 * {@link #min} and {@link #max} values.
2450 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -07002451 * Intervals are often written as {@code [min, max]} and represent the set of values
2452 * {@code x} such that {@code min <= x < max}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002453 */
Philip Milnef6679c82011-09-18 11:36:57 -07002454 final static class Interval {
Philip Milne3f8956d2011-05-13 17:29:00 +01002455 /**
2456 * The minimum value.
2457 */
2458 public final int min;
Philip Milneaa616f32011-05-27 18:38:01 -07002459
Philip Milne3f8956d2011-05-13 17:29:00 +01002460 /**
2461 * The maximum value.
2462 */
2463 public final int max;
2464
2465 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002466 * Construct a new Interval, {@code interval}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002467 * <ul>
Philip Milne7fd94872011-06-07 20:14:17 -07002468 * <li> {@code interval.min = min} </li>
2469 * <li> {@code interval.max = max} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002470 * </ul>
2471 *
2472 * @param min the minimum value.
2473 * @param max the maximum value.
2474 */
2475 public Interval(int min, int max) {
2476 this.min = min;
2477 this.max = max;
2478 }
2479
Philip Milnef6679c82011-09-18 11:36:57 -07002480 int size() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002481 return max - min;
2482 }
2483
Philip Milnef6679c82011-09-18 11:36:57 -07002484 Interval inverse() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002485 return new Interval(max, min);
2486 }
2487
2488 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002489 * Returns {@code true} if the {@link #getClass class},
2490 * {@link #min} and {@link #max} properties of this Interval and the
2491 * supplied parameter are pairwise equal; {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002492 *
Philip Milne7fd94872011-06-07 20:14:17 -07002493 * @param that the object to compare this interval with
Philip Milne3f8956d2011-05-13 17:29:00 +01002494 *
2495 * @return {@code true} if the specified object is equal to this
Philip Milne7fd94872011-06-07 20:14:17 -07002496 * {@code Interval}, {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002497 */
2498 @Override
2499 public boolean equals(Object that) {
2500 if (this == that) {
2501 return true;
2502 }
2503 if (that == null || getClass() != that.getClass()) {
2504 return false;
2505 }
2506
2507 Interval interval = (Interval) that;
2508
2509 if (max != interval.max) {
2510 return false;
2511 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002512 //noinspection RedundantIfStatement
Philip Milne3f8956d2011-05-13 17:29:00 +01002513 if (min != interval.min) {
2514 return false;
2515 }
2516
2517 return true;
2518 }
2519
2520 @Override
2521 public int hashCode() {
2522 int result = min;
2523 result = 31 * result + max;
2524 return result;
2525 }
2526
2527 @Override
2528 public String toString() {
2529 return "[" + min + ", " + max + "]";
2530 }
2531 }
2532
Philip Milne899d5922011-07-21 11:39:37 -07002533 /**
2534 * A Spec defines the horizontal or vertical characteristics of a group of
Philip Milne4a145d72011-09-29 14:14:25 -07002535 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em>
2536 * along the appropriate axis.
Philip Milne899d5922011-07-21 11:39:37 -07002537 * <p>
2538 * The <em>grid indices</em> are the leading and trailing edges of this cell group.
2539 * See {@link GridLayout} for a description of the conventions used by GridLayout
2540 * for grid indices.
2541 * <p>
2542 * The <em>alignment</em> property specifies how cells should be aligned in this group.
2543 * For row groups, this specifies the vertical alignment.
2544 * For column groups, this specifies the horizontal alignment.
Philip Milne4a145d72011-09-29 14:14:25 -07002545 * <p>
2546 * Use the following static methods to create specs:
2547 * <ul>
2548 * <li>{@link #spec(int)}</li>
2549 * <li>{@link #spec(int, int)}</li>
2550 * <li>{@link #spec(int, Alignment)}</li>
2551 * <li>{@link #spec(int, int, Alignment)}</li>
Philip Milne87260842014-05-22 13:56:03 -07002552 * <li>{@link #spec(int, float)}</li>
2553 * <li>{@link #spec(int, int, float)}</li>
2554 * <li>{@link #spec(int, Alignment, float)}</li>
2555 * <li>{@link #spec(int, int, Alignment, float)}</li>
Philip Milne4a145d72011-09-29 14:14:25 -07002556 * </ul>
2557 *
Philip Milne899d5922011-07-21 11:39:37 -07002558 */
Philip Milne93cd6a62011-07-12 14:49:45 -07002559 public static class Spec {
Philip Milnef6679c82011-09-18 11:36:57 -07002560 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
Philip Milne87260842014-05-22 13:56:03 -07002561 static final float DEFAULT_WEIGHT = 0;
Philip Milnef6679c82011-09-18 11:36:57 -07002562
2563 final boolean startDefined;
Philip Milne48b55242011-06-29 11:09:45 -07002564 final Interval span;
Philip Milne93cd6a62011-07-12 14:49:45 -07002565 final Alignment alignment;
Philip Milne87260842014-05-22 13:56:03 -07002566 final float weight;
Philip Milne3f8956d2011-05-13 17:29:00 +01002567
Philip Milne87260842014-05-22 13:56:03 -07002568 private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
Philip Milnef6679c82011-09-18 11:36:57 -07002569 this.startDefined = startDefined;
Philip Milne5d1a9842011-07-07 11:47:08 -07002570 this.span = span;
2571 this.alignment = alignment;
Philip Milne87260842014-05-22 13:56:03 -07002572 this.weight = weight;
Philip Milne5d1a9842011-07-07 11:47:08 -07002573 }
2574
Philip Milne87260842014-05-22 13:56:03 -07002575 private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
2576 this(startDefined, new Interval(start, start + size), alignment, weight);
Philip Milne93cd6a62011-07-12 14:49:45 -07002577 }
2578
Yigit Boyar38d909a2015-03-05 14:39:38 -08002579 private Alignment getAbsoluteAlignment(boolean horizontal) {
Yigit Boyar6dafd872015-03-05 13:59:56 -08002580 if (alignment != UNDEFINED_ALIGNMENT) {
2581 return alignment;
2582 }
2583 if (weight == 0f) {
2584 return horizontal ? START : BASELINE;
2585 }
2586 return FILL;
2587 }
2588
Philip Milnef6679c82011-09-18 11:36:57 -07002589 final Spec copyWriteSpan(Interval span) {
Philip Milne87260842014-05-22 13:56:03 -07002590 return new Spec(startDefined, span, alignment, weight);
Philip Milne93cd6a62011-07-12 14:49:45 -07002591 }
2592
Philip Milnef6679c82011-09-18 11:36:57 -07002593 final Spec copyWriteAlignment(Alignment alignment) {
Philip Milne87260842014-05-22 13:56:03 -07002594 return new Spec(startDefined, span, alignment, weight);
Philip Milne93cd6a62011-07-12 14:49:45 -07002595 }
2596
Philip Milnef6679c82011-09-18 11:36:57 -07002597 final int getFlexibility() {
Philip Milne87260842014-05-22 13:56:03 -07002598 return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
Philip Milne5d1a9842011-07-07 11:47:08 -07002599 }
2600
Philip Milne3f8956d2011-05-13 17:29:00 +01002601 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002602 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
2603 * properties of this Spec and the supplied parameter are pairwise equal,
Philip Milne7fd94872011-06-07 20:14:17 -07002604 * {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002605 *
Philip Milne93cd6a62011-07-12 14:49:45 -07002606 * @param that the object to compare this spec with
Philip Milne3f8956d2011-05-13 17:29:00 +01002607 *
2608 * @return {@code true} if the specified object is equal to this
Philip Milne93cd6a62011-07-12 14:49:45 -07002609 * {@code Spec}; {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +01002610 */
2611 @Override
2612 public boolean equals(Object that) {
2613 if (this == that) {
2614 return true;
2615 }
2616 if (that == null || getClass() != that.getClass()) {
2617 return false;
2618 }
2619
Philip Milne93cd6a62011-07-12 14:49:45 -07002620 Spec spec = (Spec) that;
Philip Milne3f8956d2011-05-13 17:29:00 +01002621
Philip Milne93cd6a62011-07-12 14:49:45 -07002622 if (!alignment.equals(spec.alignment)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002623 return false;
2624 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002625 //noinspection RedundantIfStatement
Philip Milne93cd6a62011-07-12 14:49:45 -07002626 if (!span.equals(spec.span)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002627 return false;
2628 }
2629
2630 return true;
2631 }
2632
2633 @Override
2634 public int hashCode() {
2635 int result = span.hashCode();
2636 result = 31 * result + alignment.hashCode();
2637 return result;
2638 }
2639 }
2640
Philip Milne3f8956d2011-05-13 17:29:00 +01002641 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002642 * Return a Spec, {@code spec}, where:
2643 * <ul>
2644 * <li> {@code spec.span = [start, start + size]} </li>
2645 * <li> {@code spec.alignment = alignment} </li>
Philip Milne87260842014-05-22 13:56:03 -07002646 * <li> {@code spec.weight = weight} </li>
Philip Milne93cd6a62011-07-12 14:49:45 -07002647 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002648 * <p>
2649 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002650 *
2651 * @param start the start
2652 * @param size the size
2653 * @param alignment the alignment
Philip Milne87260842014-05-22 13:56:03 -07002654 * @param weight the weight
2655 */
2656 public static Spec spec(int start, int size, Alignment alignment, float weight) {
2657 return new Spec(start != UNDEFINED, start, size, alignment, weight);
2658 }
2659
2660 /**
2661 * Equivalent to: {@code spec(start, 1, alignment, weight)}.
2662 *
2663 * @param start the start
2664 * @param alignment the alignment
2665 * @param weight the weight
2666 */
2667 public static Spec spec(int start, Alignment alignment, float weight) {
2668 return spec(start, 1, alignment, weight);
2669 }
2670
2671 /**
2672 * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
2673 * where {@code default_alignment} is specified in
2674 * {@link android.widget.GridLayout.LayoutParams}.
2675 *
2676 * @param start the start
2677 * @param size the size
2678 * @param weight the weight
2679 */
2680 public static Spec spec(int start, int size, float weight) {
2681 return spec(start, size, UNDEFINED_ALIGNMENT, weight);
2682 }
2683
2684 /**
2685 * Equivalent to: {@code spec(start, 1, weight)}.
2686 *
2687 * @param start the start
2688 * @param weight the weight
2689 */
2690 public static Spec spec(int start, float weight) {
2691 return spec(start, 1, weight);
2692 }
2693
2694 /**
2695 * Equivalent to: {@code spec(start, size, alignment, 0f)}.
2696 *
2697 * @param start the start
2698 * @param size the size
2699 * @param alignment the alignment
Philip Milne93cd6a62011-07-12 14:49:45 -07002700 */
2701 public static Spec spec(int start, int size, Alignment alignment) {
Philip Milne87260842014-05-22 13:56:03 -07002702 return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
Philip Milne93cd6a62011-07-12 14:49:45 -07002703 }
2704
2705 /**
2706 * Return a Spec, {@code spec}, where:
2707 * <ul>
2708 * <li> {@code spec.span = [start, start + 1]} </li>
2709 * <li> {@code spec.alignment = alignment} </li>
2710 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002711 * <p>
2712 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002713 *
2714 * @param start the start index
2715 * @param alignment the alignment
Philip Milne7b757812012-09-19 18:13:44 -07002716 *
2717 * @see #spec(int, int, Alignment)
Philip Milne93cd6a62011-07-12 14:49:45 -07002718 */
2719 public static Spec spec(int start, Alignment alignment) {
2720 return spec(start, 1, alignment);
2721 }
2722
2723 /**
Philip Milne5125e212011-07-21 11:39:37 -07002724 * Return a Spec, {@code spec}, where:
2725 * <ul>
2726 * <li> {@code spec.span = [start, start + size]} </li>
2727 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002728 * <p>
2729 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002730 *
2731 * @param start the start
2732 * @param size the size
Philip Milne7b757812012-09-19 18:13:44 -07002733 *
2734 * @see #spec(int, Alignment)
Philip Milne5125e212011-07-21 11:39:37 -07002735 */
2736 public static Spec spec(int start, int size) {
2737 return spec(start, size, UNDEFINED_ALIGNMENT);
2738 }
2739
2740 /**
2741 * Return a Spec, {@code spec}, where:
2742 * <ul>
2743 * <li> {@code spec.span = [start, start + 1]} </li>
2744 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002745 * <p>
2746 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002747 *
2748 * @param start the start index
Philip Milne7b757812012-09-19 18:13:44 -07002749 *
2750 * @see #spec(int, int)
Philip Milne5125e212011-07-21 11:39:37 -07002751 */
2752 public static Spec spec(int start) {
2753 return spec(start, 1);
2754 }
2755
2756 /**
Philip Milne3f8956d2011-05-13 17:29:00 +01002757 * Alignments specify where a view should be placed within a cell group and
2758 * what size it should be.
2759 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07002760 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
2761 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
2762 * {@code alignment}. Overall placement of the view in the cell
Philip Milne3f8956d2011-05-13 17:29:00 +01002763 * group is specified by the two alignments which act along each axis independently.
2764 * <p>
Philip Milnea1f7b102011-06-23 11:10:13 -07002765 * The GridLayout class defines the most common alignments used in general layout:
Philip Milne6216e872012-02-16 17:15:50 -08002766 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
2767 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
Philip Milnea1f7b102011-06-23 11:10:13 -07002768 */
2769 /*
Philip Milnec9885f62011-06-15 17:07:35 -07002770 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
Philip Milne3f8956d2011-05-13 17:29:00 +01002771 * to return the appropriate value for the type of alignment being defined.
2772 * The enclosing algorithms position the children
Philip Milne1e548252011-06-16 19:02:33 -07002773 * so that the locations defined by the alignment values
Philip Milne3f8956d2011-05-13 17:29:00 +01002774 * are the same for all of the views in a group.
2775 * <p>
Philip Milne3f8956d2011-05-13 17:29:00 +01002776 */
Philip Milnec9885f62011-06-15 17:07:35 -07002777 public static abstract class Alignment {
Philip Milne48b55242011-06-29 11:09:45 -07002778 Alignment() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002779 }
2780
Philip Milne6216e872012-02-16 17:15:50 -08002781 abstract int getGravityOffset(View view, int cellDelta);
2782
Philip Milne3f8956d2011-05-13 17:29:00 +01002783 /**
2784 * Returns an alignment value. In the case of vertical alignments the value
2785 * returned should indicate the distance from the top of the view to the
2786 * alignment location.
2787 * For horizontal alignments measurement is made from the left edge of the component.
2788 *
Philip Milnec9885f62011-06-15 17:07:35 -07002789 * @param view the view to which this alignment should be applied
2790 * @param viewSize the measured size of the view
Philip Milne7a23b492012-04-24 22:12:36 -07002791 * @param mode the basis of alignment: CLIP or OPTICAL
Philip Milneb3a8c542011-06-20 16:02:59 -07002792 * @return the alignment value
Philip Milne3f8956d2011-05-13 17:29:00 +01002793 */
Philip Milne7a23b492012-04-24 22:12:36 -07002794 abstract int getAlignmentValue(View view, int viewSize, int mode);
Philip Milne3f8956d2011-05-13 17:29:00 +01002795
2796 /**
2797 * Returns the size of the view specified by this alignment.
2798 * In the case of vertical alignments this method should return a height; for
2799 * horizontal alignments this method should return the width.
Philip Milnec9885f62011-06-15 17:07:35 -07002800 * <p>
2801 * The default implementation returns {@code viewSize}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002802 *
Philip Milnec9885f62011-06-15 17:07:35 -07002803 * @param view the view to which this alignment should be applied
2804 * @param viewSize the measured size of the view
2805 * @param cellSize the size of the cell into which this view will be placed
Philip Milneb3a8c542011-06-20 16:02:59 -07002806 * @return the aligned size
Philip Milne3f8956d2011-05-13 17:29:00 +01002807 */
Philip Milne6216e872012-02-16 17:15:50 -08002808 int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002809 return viewSize;
2810 }
Philip Milnea1f7b102011-06-23 11:10:13 -07002811
Philip Milne48b55242011-06-29 11:09:45 -07002812 Bounds getBounds() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002813 return new Bounds();
2814 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002815 }
2816
Mathew Inwood978c6e22018-08-21 15:58:55 +01002817 @UnsupportedAppUsage
Philip Milnef6679c82011-09-18 11:36:57 -07002818 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002819 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002820 int getGravityOffset(View view, int cellDelta) {
2821 return UNDEFINED;
2822 }
2823
2824 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002825 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne5125e212011-07-21 11:39:37 -07002826 return UNDEFINED;
2827 }
2828 };
2829
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002830 /**
2831 * Indicates that a view should be aligned with the <em>start</em>
2832 * edges of the other views in its cell group.
2833 */
Philip Milnec9885f62011-06-15 17:07:35 -07002834 private static final Alignment LEADING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002835 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002836 int getGravityOffset(View view, int cellDelta) {
2837 return 0;
2838 }
2839
2840 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002841 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002842 return 0;
2843 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002844 };
2845
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002846 /**
2847 * Indicates that a view should be aligned with the <em>end</em>
2848 * edges of the other views in its cell group.
2849 */
Philip Milnec9885f62011-06-15 17:07:35 -07002850 private static final Alignment TRAILING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002851 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002852 int getGravityOffset(View view, int cellDelta) {
2853 return cellDelta;
2854 }
2855
2856 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002857 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002858 return viewSize;
2859 }
2860 };
2861
2862 /**
2863 * Indicates that a view should be aligned with the <em>top</em>
2864 * edges of the other views in its cell group.
2865 */
2866 public static final Alignment TOP = LEADING;
2867
2868 /**
2869 * Indicates that a view should be aligned with the <em>bottom</em>
2870 * edges of the other views in its cell group.
2871 */
2872 public static final Alignment BOTTOM = TRAILING;
2873
2874 /**
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002875 * Indicates that a view should be aligned with the <em>start</em>
Philip Milne3f8956d2011-05-13 17:29:00 +01002876 * edges of the other views in its cell group.
2877 */
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002878 public static final Alignment START = LEADING;
2879
2880 /**
2881 * Indicates that a view should be aligned with the <em>end</em>
2882 * edges of the other views in its cell group.
2883 */
2884 public static final Alignment END = TRAILING;
2885
Philip Milne6216e872012-02-16 17:15:50 -08002886 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002887 return new Alignment() {
2888 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002889 int getGravityOffset(View view, int cellDelta) {
2890 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta);
2891 }
2892
2893 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002894 public int getAlignmentValue(View view, int viewSize, int mode) {
2895 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002896 }
2897 };
2898 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002899
2900 /**
2901 * Indicates that a view should be aligned with the <em>left</em>
2902 * edges of the other views in its cell group.
2903 */
Philip Milne6216e872012-02-16 17:15:50 -08002904 public static final Alignment LEFT = createSwitchingAlignment(START, END);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002905
2906 /**
2907 * Indicates that a view should be aligned with the <em>right</em>
2908 * edges of the other views in its cell group.
2909 */
Philip Milne6216e872012-02-16 17:15:50 -08002910 public static final Alignment RIGHT = createSwitchingAlignment(END, START);
Philip Milne3f8956d2011-05-13 17:29:00 +01002911
2912 /**
2913 * Indicates that a view should be <em>centered</em> with the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002914 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
2915 * LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002916 */
Philip Milnec9885f62011-06-15 17:07:35 -07002917 public static final Alignment CENTER = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002918 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002919 int getGravityOffset(View view, int cellDelta) {
2920 return cellDelta >> 1;
2921 }
2922
2923 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002924 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002925 return viewSize >> 1;
2926 }
2927 };
2928
2929 /**
2930 * Indicates that a view should be aligned with the <em>baselines</em>
2931 * of the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002932 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002933 *
2934 * @see View#getBaseline()
2935 */
Philip Milnec9885f62011-06-15 17:07:35 -07002936 public static final Alignment BASELINE = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002937 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002938 int getGravityOffset(View view, int cellDelta) {
2939 return 0; // baseline gravity is top
2940 }
2941
2942 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002943 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milnea8416442013-02-25 10:49:39 -08002944 if (view.getVisibility() == GONE) {
2945 return 0;
2946 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002947 int baseline = view.getBaseline();
Philip Milne7b757812012-09-19 18:13:44 -07002948 return baseline == -1 ? UNDEFINED : baseline;
Philip Milnea1f7b102011-06-23 11:10:13 -07002949 }
2950
2951 @Override
2952 public Bounds getBounds() {
2953 return new Bounds() {
2954 /*
2955 In a baseline aligned row in which some components define a baseline
2956 and some don't, we need a third variable to properly account for all
2957 the sizes. This tracks the maximum size of all the components -
2958 including those that don't define a baseline.
2959 */
2960 private int size;
2961
2962 @Override
2963 protected void reset() {
2964 super.reset();
Philip Milne48b55242011-06-29 11:09:45 -07002965 size = Integer.MIN_VALUE;
Philip Milnea1f7b102011-06-23 11:10:13 -07002966 }
2967
2968 @Override
2969 protected void include(int before, int after) {
2970 super.include(before, after);
2971 size = max(size, before + after);
2972 }
2973
2974 @Override
Philip Milne48b55242011-06-29 11:09:45 -07002975 protected int size(boolean min) {
2976 return max(super.size(min), size);
Philip Milnea1f7b102011-06-23 11:10:13 -07002977 }
2978
2979 @Override
Philip Milne1557fd72012-04-04 23:41:34 -07002980 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
2981 return max(0, super.getOffset(gl, c, a, size, hrz));
Philip Milnea1f7b102011-06-23 11:10:13 -07002982 }
2983 };
Philip Milne3f8956d2011-05-13 17:29:00 +01002984 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002985 };
2986
2987 /**
2988 * Indicates that a view should expanded to fit the boundaries of its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002989 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
2990 * {@link LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002991 */
2992 public static final Alignment FILL = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002993 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002994 int getGravityOffset(View view, int cellDelta) {
2995 return 0;
2996 }
2997
2998 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002999 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01003000 return UNDEFINED;
3001 }
3002
Philip Milnec9885f62011-06-15 17:07:35 -07003003 @Override
Philip Milne6216e872012-02-16 17:15:50 -08003004 public int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01003005 return cellSize;
3006 }
3007 };
Philip Milne48b55242011-06-29 11:09:45 -07003008
Philip Milnef6679c82011-09-18 11:36:57 -07003009 static boolean canStretch(int flexibility) {
Philip Milne5d1a9842011-07-07 11:47:08 -07003010 return (flexibility & CAN_STRETCH) != 0;
3011 }
3012
Philip Milne5125e212011-07-21 11:39:37 -07003013 private static final int INFLEXIBLE = 0;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07003014 private static final int CAN_STRETCH = 2;
Jim Miller452eec32011-06-16 18:32:44 -07003015}