blob: b0ab70d51c35ff83d87d651e9752b315bf4b7f0b [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
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.Color;
Philip Milne1557fd72012-04-04 23:41:34 -070023import android.graphics.Insets;
Philip Milne3f8956d2011-05-13 17:29:00 +010024import android.graphics.Paint;
Philip Milne3f8956d2011-05-13 17:29:00 +010025import android.util.AttributeSet;
26import android.util.Log;
Philip Milne211d0332012-04-24 14:45:14 -070027import android.util.LogPrinter;
Philip Milne48b55242011-06-29 11:09:45 -070028import android.util.Pair;
Philip Milne211d0332012-04-24 14:45:14 -070029import android.util.Printer;
Philip Milne3f8956d2011-05-13 17:29:00 +010030import android.view.Gravity;
31import android.view.View;
32import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080033import android.view.accessibility.AccessibilityEvent;
34import android.view.accessibility.AccessibilityNodeInfo;
Philip Milneedd69512012-03-14 17:21:33 -070035import android.widget.RemoteViews.RemoteView;
Philip Milne10ca24a2012-04-23 15:38:27 -070036import com.android.internal.R;
Philip Milne3f8956d2011-05-13 17:29:00 +010037
38import java.lang.reflect.Array;
39import java.util.ArrayList;
40import java.util.Arrays;
Philip Milne3f8956d2011-05-13 17:29:00 +010041import java.util.HashMap;
42import java.util.List;
43import java.util.Map;
44
Philip Milne5125e212011-07-21 11:39:37 -070045import static android.view.Gravity.*;
Philip Milne899d5922011-07-21 11:39:37 -070046import static android.view.View.MeasureSpec.EXACTLY;
47import static android.view.View.MeasureSpec.makeMeasureSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +010048import static java.lang.Math.max;
49import static java.lang.Math.min;
50
51/**
52 * A layout that places its children in a rectangular <em>grid</em>.
53 * <p>
54 * The grid is composed of a set of infinitely thin lines that separate the
55 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
Philip Milne7fd94872011-06-07 20:14:17 -070056 * by grid <em>indices</em>. A grid with {@code N} columns
57 * has {@code N + 1} grid indices that run from {@code 0}
58 * through {@code N} inclusive. Regardless of how GridLayout is
59 * configured, grid index {@code 0} is fixed to the leading edge of the
60 * container and grid index {@code N} is fixed to its trailing edge
Philip Milne3f8956d2011-05-13 17:29:00 +010061 * (after padding is taken into account).
62 *
Philip Milne93cd6a62011-07-12 14:49:45 -070063 * <h4>Row and Column Specs</h4>
Philip Milne3f8956d2011-05-13 17:29:00 +010064 *
65 * Children occupy one or more contiguous cells, as defined
Philip Milne93cd6a62011-07-12 14:49:45 -070066 * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and
67 * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters.
68 * Each spec defines the set of rows or columns that are to be
Philip Milne3f8956d2011-05-13 17:29:00 +010069 * occupied; and how children should be aligned within the resulting group of cells.
70 * Although cells do not normally overlap in a GridLayout, GridLayout does
71 * not prevent children being defined to occupy the same cell or group of cells.
72 * In this case however, there is no guarantee that children will not themselves
73 * overlap after the layout operation completes.
74 *
75 * <h4>Default Cell Assignment</h4>
76 *
Philip Milne48b55242011-06-29 11:09:45 -070077 * If a child does not specify the row and column indices of the cell it
Philip Milne3f8956d2011-05-13 17:29:00 +010078 * wishes to occupy, GridLayout assigns cell locations automatically using its:
79 * {@link GridLayout#setOrientation(int) orientation},
80 * {@link GridLayout#setRowCount(int) rowCount} and
81 * {@link GridLayout#setColumnCount(int) columnCount} properties.
82 *
83 * <h4>Space</h4>
84 *
85 * Space between children may be specified either by using instances of the
86 * dedicated {@link Space} view or by setting the
87 *
88 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
89 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
90 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
91 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
92 *
93 * layout parameters. When the
94 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
95 * property is set, default margins around children are automatically
Philip Milnef6679c82011-09-18 11:36:57 -070096 * allocated based on the prevailing UI style guide for the platform.
97 * Each of the margins so defined may be independently overridden by an assignment
Philip Milne3f8956d2011-05-13 17:29:00 +010098 * to the appropriate layout parameter.
Philip Milnef6679c82011-09-18 11:36:57 -070099 * Default values will generally produce a reasonable spacing between components
100 * but values may change between different releases of the platform.
Philip Milne3f8956d2011-05-13 17:29:00 +0100101 *
102 * <h4>Excess Space Distribution</h4>
103 *
Philip Milnef6679c82011-09-18 11:36:57 -0700104 * GridLayout's distribution of excess space is based on <em>priority</em>
105 * rather than <em>weight</em>.
106 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700107 * A child's ability to stretch is inferred from the alignment properties of
108 * its row and column groups (which are typically set by setting the
109 * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
110 * If alignment was defined along a given axis then the component
Philip Milnef6679c82011-09-18 11:36:57 -0700111 * is taken as <em>flexible</em> in that direction. If no alignment was set,
112 * the component is instead assumed to be <em>inflexible</em>.
113 * <p>
114 * Multiple components in the same row or column group are
115 * considered to act in <em>parallel</em>. Such a
Philip Milne899d5922011-07-21 11:39:37 -0700116 * group is flexible only if <em>all</em> of the components
117 * within it are flexible. Row and column groups that sit either side of a common boundary
118 * are instead considered to act in <em>series</em>. The composite group made of these two
119 * elements is flexible if <em>one</em> of its elements is flexible.
120 * <p>
121 * To make a column stretch, make sure all of the components inside it define a
122 * gravity. To prevent a column from stretching, ensure that one of the components
123 * in the column does not define a gravity.
Philip Milne3f8956d2011-05-13 17:29:00 +0100124 * <p>
Philip Milnef6679c82011-09-18 11:36:57 -0700125 * When the principle of flexibility does not provide complete disambiguation,
126 * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
127 * and <em>bottom</em> edges.
128 *
Philip Milnea8416442013-02-25 10:49:39 -0800129 * <h4>Interpretation of GONE</h4>
130 *
131 * For layout purposes, GridLayout treats views whose visibility status is
132 * {@link View#GONE GONE}, as having zero width and height. This is subtly different from
133 * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked
134 * view was alone in a column, that column would itself collapse to zero width if and only if
135 * no gravity was defined on the view. If gravity was defined, then the gone-marked
136 * view has no effect on the layout and the container should be laid out as if the view
137 * had never been added to it.
138 * These statements apply equally to rows as well as columns, and to groups of rows or columns.
139 *
Philip Milnef6679c82011-09-18 11:36:57 -0700140 * <h5>Limitations</h5>
141 *
142 * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
143 * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
Philip Milne6216e872012-02-16 17:15:50 -0800144 * to configure a GridLayout to distribute excess space between multiple components.
Philip Milnef6679c82011-09-18 11:36:57 -0700145 * <p>
146 * Some common use-cases may nevertheless be accommodated as follows.
147 * To place equal amounts of space around a component in a cell group;
148 * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
149 * For complete control over excess space distribution in a row or column;
150 * use a {@link LinearLayout} subview to hold the components in the associated cell group.
151 * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
Philip Milne3f8956d2011-05-13 17:29:00 +0100152 * <p>
153 * See {@link GridLayout.LayoutParams} for a full description of the
154 * layout parameters used by GridLayout.
155 *
156 * @attr ref android.R.styleable#GridLayout_orientation
157 * @attr ref android.R.styleable#GridLayout_rowCount
158 * @attr ref android.R.styleable#GridLayout_columnCount
159 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
160 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
161 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
162 */
Philip Milneedd69512012-03-14 17:21:33 -0700163@RemoteView
Philip Milne3f8956d2011-05-13 17:29:00 +0100164public class GridLayout extends ViewGroup {
165
166 // Public constants
167
168 /**
169 * The horizontal orientation.
170 */
171 public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
Philip Milneaa616f32011-05-27 18:38:01 -0700172
Philip Milne3f8956d2011-05-13 17:29:00 +0100173 /**
174 * The vertical orientation.
175 */
176 public static final int VERTICAL = LinearLayout.VERTICAL;
177
Philip Milneaa616f32011-05-27 18:38:01 -0700178 /**
179 * The constant used to indicate that a value is undefined.
180 * Fields can use this value to indicate that their values
181 * have not yet been set. Similarly, methods can return this value
182 * to indicate that there is no suitable value that the implementation
183 * can return.
184 * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
185 * intended to avoid confusion between valid values whose sign may not be known.
186 */
187 public static final int UNDEFINED = Integer.MIN_VALUE;
188
Philip Milne1e548252011-06-16 19:02:33 -0700189 /**
190 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
191 * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
192 * is made between the edges of each component's raw
193 * view boundary: i.e. the area delimited by the component's:
194 * {@link android.view.View#getTop() top},
195 * {@link android.view.View#getLeft() left},
196 * {@link android.view.View#getBottom() bottom} and
197 * {@link android.view.View#getRight() right} properties.
198 * <p>
199 * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode,
200 * children that belong to a row group that uses {@link #TOP} alignment will
201 * all return the same value when their {@link android.view.View#getTop()}
202 * method is called.
203 *
204 * @see #setAlignmentMode(int)
205 */
206 public static final int ALIGN_BOUNDS = 0;
207
208 /**
209 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
210 * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS},
211 * the bounds of each view are extended outwards, according
212 * to their margins, before the edges of the resulting rectangle are aligned.
213 * <p>
214 * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode,
215 * the quantity {@code top - layoutParams.topMargin} is the same for all children that
216 * belong to a row group that uses {@link #TOP} alignment.
217 *
218 * @see #setAlignmentMode(int)
219 */
220 public static final int ALIGN_MARGINS = 1;
221
Philip Milne3f8956d2011-05-13 17:29:00 +0100222 // Misc constants
223
Philip Milnef6679c82011-09-18 11:36:57 -0700224 static final int MAX_SIZE = 100000;
225 static final int DEFAULT_CONTAINER_MARGIN = 0;
Philip Milned7dd8902012-01-26 16:55:30 -0800226 static final int UNINITIALIZED_HASH = 0;
Philip Milnea2353622013-03-05 12:19:15 -0800227 static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName());
228 static final Printer NO_PRINTER = new Printer() {
229 @Override
230 public void println(String x) {
231 }
232 };
Philip Milne3f8956d2011-05-13 17:29:00 +0100233
234 // Defaults
235
236 private static final int DEFAULT_ORIENTATION = HORIZONTAL;
237 private static final int DEFAULT_COUNT = UNDEFINED;
238 private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
Philip Milne899d5922011-07-21 11:39:37 -0700239 private static final boolean DEFAULT_ORDER_PRESERVED = true;
Philip Milne1e548252011-06-16 19:02:33 -0700240 private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
Philip Milne3f8956d2011-05-13 17:29:00 +0100241
242 // TypedArray indices
243
Philip Milneb0ce49b2011-07-15 15:39:07 -0700244 private static final int ORIENTATION = R.styleable.GridLayout_orientation;
245 private static final int ROW_COUNT = R.styleable.GridLayout_rowCount;
246 private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount;
247 private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins;
248 private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode;
249 private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved;
250 private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +0100251
252 // Instance variables
253
Philip Milnef6679c82011-09-18 11:36:57 -0700254 final Axis horizontalAxis = new Axis(true);
255 final Axis verticalAxis = new Axis(false);
Philip Milnef6679c82011-09-18 11:36:57 -0700256 int orientation = DEFAULT_ORIENTATION;
257 boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
258 int alignmentMode = DEFAULT_ALIGNMENT_MODE;
259 int defaultGap;
Philip Milned7dd8902012-01-26 16:55:30 -0800260 int lastLayoutParamsHashCode = UNINITIALIZED_HASH;
Philip Milnea2353622013-03-05 12:19:15 -0800261 Printer printer = LOG_PRINTER;
Philip Milneaa616f32011-05-27 18:38:01 -0700262
Philip Milne3f8956d2011-05-13 17:29:00 +0100263 // Constructors
264
265 /**
266 * {@inheritDoc}
267 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100268 public GridLayout(Context context, AttributeSet attrs, int defStyle) {
269 super(context, attrs, defStyle);
Philip Milnef6679c82011-09-18 11:36:57 -0700270 defaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
Philip Milneb0ce49b2011-07-15 15:39:07 -0700271 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout);
Philip Milne3f8956d2011-05-13 17:29:00 +0100272 try {
Philip Milne1e548252011-06-16 19:02:33 -0700273 setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
274 setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
Philip Milne5125e212011-07-21 11:39:37 -0700275 setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION));
276 setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS));
277 setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE));
Philip Milne3f8956d2011-05-13 17:29:00 +0100278 setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
279 setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
280 } finally {
281 a.recycle();
282 }
283 }
284
Philip Milne48b55242011-06-29 11:09:45 -0700285 /**
286 * {@inheritDoc}
287 */
288 public GridLayout(Context context, AttributeSet attrs) {
289 this(context, attrs, 0);
290 }
291
292 /**
293 * {@inheritDoc}
294 */
295 public GridLayout(Context context) {
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700296 //noinspection NullableProblems
Philip Milne48b55242011-06-29 11:09:45 -0700297 this(context, null);
298 }
299
Philip Milne3f8956d2011-05-13 17:29:00 +0100300 // Implementation
301
302 /**
303 * Returns the current orientation.
304 *
Philip Milne7fd94872011-06-07 20:14:17 -0700305 * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100306 *
307 * @see #setOrientation(int)
308 *
309 * @attr ref android.R.styleable#GridLayout_orientation
310 */
311 public int getOrientation() {
Philip Milnef6679c82011-09-18 11:36:57 -0700312 return orientation;
Philip Milne3f8956d2011-05-13 17:29:00 +0100313 }
314
315 /**
Philip Milneb65408f2012-05-21 10:44:46 -0700316 *
317 * GridLayout uses the orientation property for two purposes:
318 * <ul>
319 * <li>
320 * To control the 'direction' in which default row/column indices are generated
321 * when they are not specified in a component's layout parameters.
322 * </li>
323 * <li>
324 * To control which axis should be processed first during the layout operation:
325 * when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first.
326 * </li>
327 * </ul>
328 *
329 * The order in which axes are laid out is important if, for example, the height of
330 * one of GridLayout's children is dependent on its width - and its width is, in turn,
331 * dependent on the widths of other components.
332 * <p>
333 * If your layout contains a {@link TextView} (or derivative:
334 * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is
335 * in multi-line mode (the default) it is normally best to leave GridLayout's
336 * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of
337 * deriving its height for a given width, but not the other way around.
338 * <p>
339 * Other than the effects above, orientation does not affect the actual layout operation of
340 * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if
341 * the height of the intended layout greatly exceeds its width.
Philip Milne7fd94872011-06-07 20:14:17 -0700342 * <p>
343 * The default value of this property is {@link #HORIZONTAL}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100344 *
Philip Milne7fd94872011-06-07 20:14:17 -0700345 * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100346 *
347 * @see #getOrientation()
348 *
349 * @attr ref android.R.styleable#GridLayout_orientation
350 */
351 public void setOrientation(int orientation) {
Philip Milnef6679c82011-09-18 11:36:57 -0700352 if (this.orientation != orientation) {
353 this.orientation = orientation;
354 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100355 requestLayout();
356 }
357 }
358
359 /**
360 * Returns the current number of rows. This is either the last value that was set
361 * with {@link #setRowCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700362 * value of each the upper bounds defined in {@link LayoutParams#rowSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100363 *
364 * @return the current number of rows
365 *
366 * @see #setRowCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700367 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100368 *
369 * @attr ref android.R.styleable#GridLayout_rowCount
370 */
371 public int getRowCount() {
Philip Milnef6679c82011-09-18 11:36:57 -0700372 return verticalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100373 }
374
375 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700376 * RowCount is used only to generate default row/column indices when
377 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100378 *
Philip Milne7fd94872011-06-07 20:14:17 -0700379 * @param rowCount the number of rows
Philip Milne3f8956d2011-05-13 17:29:00 +0100380 *
381 * @see #getRowCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700382 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100383 *
384 * @attr ref android.R.styleable#GridLayout_rowCount
385 */
386 public void setRowCount(int rowCount) {
Philip Milnef6679c82011-09-18 11:36:57 -0700387 verticalAxis.setCount(rowCount);
388 invalidateStructure();
389 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100390 }
391
392 /**
393 * Returns the current number of columns. This is either the last value that was set
394 * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700395 * value of each the upper bounds defined in {@link LayoutParams#columnSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100396 *
397 * @return the current number of columns
398 *
399 * @see #setColumnCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700400 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100401 *
402 * @attr ref android.R.styleable#GridLayout_columnCount
403 */
404 public int getColumnCount() {
Philip Milnef6679c82011-09-18 11:36:57 -0700405 return horizontalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100406 }
407
408 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700409 * ColumnCount is used only to generate default column/column indices when
410 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100411 *
412 * @param columnCount the number of columns.
413 *
414 * @see #getColumnCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700415 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100416 *
417 * @attr ref android.R.styleable#GridLayout_columnCount
418 */
419 public void setColumnCount(int columnCount) {
Philip Milnef6679c82011-09-18 11:36:57 -0700420 horizontalAxis.setCount(columnCount);
421 invalidateStructure();
422 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100423 }
424
425 /**
426 * Returns whether or not this GridLayout will allocate default margins when no
427 * corresponding layout parameters are defined.
428 *
Philip Milne7fd94872011-06-07 20:14:17 -0700429 * @return {@code true} if default margins should be allocated
Philip Milne3f8956d2011-05-13 17:29:00 +0100430 *
431 * @see #setUseDefaultMargins(boolean)
432 *
433 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
434 */
435 public boolean getUseDefaultMargins() {
Philip Milnef6679c82011-09-18 11:36:57 -0700436 return useDefaultMargins;
Philip Milne3f8956d2011-05-13 17:29:00 +0100437 }
438
439 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700440 * When {@code true}, GridLayout allocates default margins around children
Philip Milne3f8956d2011-05-13 17:29:00 +0100441 * based on the child's visual characteristics. Each of the
442 * margins so defined may be independently overridden by an assignment
443 * to the appropriate layout parameter.
444 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700445 * When {@code false}, the default value of all margins is zero.
Philip Milneaa616f32011-05-27 18:38:01 -0700446 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700447 * When setting to {@code true}, consider setting the value of the
Philip Milne1e548252011-06-16 19:02:33 -0700448 * {@link #setAlignmentMode(int) alignmentMode}
449 * property to {@link #ALIGN_BOUNDS}.
Philip Milne7fd94872011-06-07 20:14:17 -0700450 * <p>
451 * The default value of this property is {@code false}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100452 *
Philip Milne7fd94872011-06-07 20:14:17 -0700453 * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
Philip Milne3f8956d2011-05-13 17:29:00 +0100454 *
455 * @see #getUseDefaultMargins()
Philip Milne1e548252011-06-16 19:02:33 -0700456 * @see #setAlignmentMode(int)
Philip Milne3f8956d2011-05-13 17:29:00 +0100457 *
458 * @see MarginLayoutParams#leftMargin
459 * @see MarginLayoutParams#topMargin
460 * @see MarginLayoutParams#rightMargin
461 * @see MarginLayoutParams#bottomMargin
462 *
463 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
464 */
465 public void setUseDefaultMargins(boolean useDefaultMargins) {
Philip Milnef6679c82011-09-18 11:36:57 -0700466 this.useDefaultMargins = useDefaultMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700467 requestLayout();
468 }
469
470 /**
Philip Milne1e548252011-06-16 19:02:33 -0700471 * Returns the alignment mode.
Philip Milneaa616f32011-05-27 18:38:01 -0700472 *
Philip Milne1e548252011-06-16 19:02:33 -0700473 * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700474 *
Philip Milne1e548252011-06-16 19:02:33 -0700475 * @see #ALIGN_BOUNDS
476 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700477 *
Philip Milne1e548252011-06-16 19:02:33 -0700478 * @see #setAlignmentMode(int)
479 *
480 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700481 */
Philip Milne1e548252011-06-16 19:02:33 -0700482 public int getAlignmentMode() {
Philip Milnef6679c82011-09-18 11:36:57 -0700483 return alignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700484 }
485
486 /**
Philip Milne1e548252011-06-16 19:02:33 -0700487 * Sets the alignment mode to be used for all of the alignments between the
488 * children of this container.
Philip Milne7fd94872011-06-07 20:14:17 -0700489 * <p>
Philip Milne1e548252011-06-16 19:02:33 -0700490 * The default value of this property is {@link #ALIGN_MARGINS}.
Philip Milneaa616f32011-05-27 18:38:01 -0700491 *
Philip Milne1e548252011-06-16 19:02:33 -0700492 * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700493 *
Philip Milne1e548252011-06-16 19:02:33 -0700494 * @see #ALIGN_BOUNDS
495 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700496 *
Philip Milne1e548252011-06-16 19:02:33 -0700497 * @see #getAlignmentMode()
498 *
499 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700500 */
Philip Milne1e548252011-06-16 19:02:33 -0700501 public void setAlignmentMode(int alignmentMode) {
Philip Milnef6679c82011-09-18 11:36:57 -0700502 this.alignmentMode = alignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700503 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100504 }
505
506 /**
507 * Returns whether or not row boundaries are ordered by their grid indices.
508 *
Philip Milne7fd94872011-06-07 20:14:17 -0700509 * @return {@code true} if row boundaries must appear in the order of their indices,
510 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100511 *
512 * @see #setRowOrderPreserved(boolean)
513 *
514 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
515 */
516 public boolean isRowOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -0700517 return verticalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100518 }
519
520 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700521 * When this property is {@code true}, GridLayout is forced to place the row boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700522 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100523 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700524 * When this property is {@code false} GridLayout is at liberty to place the vertical row
525 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700526 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700527 * The default value of this property is {@code true}.
Philip Milne7fd94872011-06-07 20:14:17 -0700528
529 * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
530 * of row boundaries
Philip Milne3f8956d2011-05-13 17:29:00 +0100531 *
532 * @see #isRowOrderPreserved()
533 *
534 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
535 */
536 public void setRowOrderPreserved(boolean rowOrderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -0700537 verticalAxis.setOrderPreserved(rowOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700538 invalidateStructure();
539 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100540 }
541
542 /**
543 * Returns whether or not column boundaries are ordered by their grid indices.
544 *
Philip Milne7fd94872011-06-07 20:14:17 -0700545 * @return {@code true} if column boundaries must appear in the order of their indices,
546 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100547 *
548 * @see #setColumnOrderPreserved(boolean)
549 *
550 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
551 */
552 public boolean isColumnOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -0700553 return horizontalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100554 }
555
556 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700557 * When this property is {@code true}, GridLayout is forced to place the column boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700558 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100559 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700560 * When this property is {@code false} GridLayout is at liberty to place the horizontal column
561 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700562 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700563 * The default value of this property is {@code true}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100564 *
Philip Milne7fd94872011-06-07 20:14:17 -0700565 * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
Philip Milne3f8956d2011-05-13 17:29:00 +0100566 * of column boundaries.
567 *
568 * @see #isColumnOrderPreserved()
569 *
570 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
571 */
572 public void setColumnOrderPreserved(boolean columnOrderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -0700573 horizontalAxis.setOrderPreserved(columnOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700574 invalidateStructure();
575 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100576 }
577
Philip Milne211d0332012-04-24 14:45:14 -0700578 /**
579 * Return the printer that will log diagnostics from this layout.
580 *
581 * @see #setPrinter(android.util.Printer)
582 *
583 * @return the printer associated with this view
584 */
585 public Printer getPrinter() {
586 return printer;
587 }
588
589 /**
590 * Set the printer that will log diagnostics from this layout.
591 * The default value is created by {@link android.util.LogPrinter}.
592 *
593 * @param printer the printer associated with this layout
594 *
595 * @see #getPrinter()
596 */
597 public void setPrinter(Printer printer) {
Philip Milnea2353622013-03-05 12:19:15 -0800598 this.printer = (printer == null) ? NO_PRINTER : printer;
Philip Milne211d0332012-04-24 14:45:14 -0700599 }
600
Philip Milne5125e212011-07-21 11:39:37 -0700601 // Static utility methods
602
Philip Milnef6679c82011-09-18 11:36:57 -0700603 static int max2(int[] a, int valueIfEmpty) {
Philip Milne51f17d52011-06-13 10:44:49 -0700604 int result = valueIfEmpty;
605 for (int i = 0, N = a.length; i < N; i++) {
606 result = Math.max(result, a[i]);
607 }
608 return result;
609 }
610
Philip Milne899d5922011-07-21 11:39:37 -0700611 @SuppressWarnings("unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -0700612 static <T> T[] append(T[] a, T[] b) {
Philip Milne48b55242011-06-29 11:09:45 -0700613 T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
614 System.arraycopy(a, 0, result, 0, a.length);
615 System.arraycopy(b, 0, result, a.length, b.length);
Philip Milne3f8956d2011-05-13 17:29:00 +0100616 return result;
617 }
618
Philip Milnef6679c82011-09-18 11:36:57 -0700619 static Alignment getAlignment(int gravity, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -0700620 int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
621 int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
622 int flags = (gravity & mask) >> shift;
623 switch (flags) {
624 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
Philip Milne1557fd72012-04-04 23:41:34 -0700625 return horizontal ? LEFT : TOP;
Philip Milne5125e212011-07-21 11:39:37 -0700626 case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
Philip Milne1557fd72012-04-04 23:41:34 -0700627 return horizontal ? RIGHT : BOTTOM;
Philip Milne5125e212011-07-21 11:39:37 -0700628 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
629 return FILL;
630 case AXIS_SPECIFIED:
631 return CENTER;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800632 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION):
633 return START;
634 case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION):
635 return END;
Philip Milne5125e212011-07-21 11:39:37 -0700636 default:
637 return UNDEFINED_ALIGNMENT;
638 }
639 }
640
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700641 /** @noinspection UnusedParameters*/
Philip Milne1fd16372011-06-21 14:57:47 -0700642 private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
Philip Milne899d5922011-07-21 11:39:37 -0700643 if (c.getClass() == Space.class) {
644 return 0;
645 }
Philip Milnef6679c82011-09-18 11:36:57 -0700646 return defaultGap / 2;
Philip Milne3f8956d2011-05-13 17:29:00 +0100647 }
648
Philip Milne1fd16372011-06-21 14:57:47 -0700649 private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
Philip Milne7b757812012-09-19 18:13:44 -0700650 return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100651 }
652
Philip Milne7a23b492012-04-24 22:12:36 -0700653 private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
Philip Milnef6679c82011-09-18 11:36:57 -0700654 if (!useDefaultMargins) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100655 return 0;
656 }
Philip Milne93cd6a62011-07-12 14:49:45 -0700657 Spec spec = horizontal ? p.columnSpec : p.rowSpec;
Philip Milnef6679c82011-09-18 11:36:57 -0700658 Axis axis = horizontal ? horizontalAxis : verticalAxis;
Philip Milne93cd6a62011-07-12 14:49:45 -0700659 Interval span = spec.span;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800660 boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading;
661 boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
Philip Milne3f8956d2011-05-13 17:29:00 +0100662
Philip Milne1fd16372011-06-21 14:57:47 -0700663 return getDefaultMargin(c, isAtEdge, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100664 }
665
Philip Milnef6679c82011-09-18 11:36:57 -0700666 int getMargin1(View view, boolean horizontal, boolean leading) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100667 LayoutParams lp = getLayoutParams(view);
668 int margin = horizontal ?
Philip Milneaa616f32011-05-27 18:38:01 -0700669 (leading ? lp.leftMargin : lp.rightMargin) :
670 (leading ? lp.topMargin : lp.bottomMargin);
Philip Milne7a23b492012-04-24 22:12:36 -0700671 return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin;
Philip Milne1fd16372011-06-21 14:57:47 -0700672 }
673
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700674 private int getMargin(View view, boolean horizontal, boolean leading) {
Philip Milnef6679c82011-09-18 11:36:57 -0700675 if (alignmentMode == ALIGN_MARGINS) {
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700676 return getMargin1(view, horizontal, leading);
677 } else {
Philip Milnef6679c82011-09-18 11:36:57 -0700678 Axis axis = horizontal ? horizontalAxis : verticalAxis;
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700679 int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
680 LayoutParams lp = getLayoutParams(view);
681 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
682 int index = leading ? spec.span.min : spec.span.max;
683 return margins[index];
684 }
685 }
686
Philip Milne1fd16372011-06-21 14:57:47 -0700687 private int getTotalMargin(View child, boolean horizontal) {
688 return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
Philip Milne3f8956d2011-05-13 17:29:00 +0100689 }
690
Philip Milne899d5922011-07-21 11:39:37 -0700691 private static boolean fits(int[] a, int value, int start, int end) {
692 if (end > a.length) {
693 return false;
694 }
695 for (int i = start; i < end; i++) {
696 if (a[i] > value) {
697 return false;
698 }
699 }
700 return true;
701 }
702
703 private static void procrusteanFill(int[] a, int start, int end, int value) {
704 int length = a.length;
705 Arrays.fill(a, Math.min(start, length), Math.min(end, length), value);
706 }
707
708 private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) {
709 lp.setRowSpecSpan(new Interval(row, row + rowSpan));
710 lp.setColumnSpecSpan(new Interval(col, col + colSpan));
711 }
712
713 // Logic to avert infinite loops by ensuring that the cells can be placed somewhere.
714 private static int clip(Interval minorRange, boolean minorWasDefined, int count) {
715 int size = minorRange.size();
716 if (count == 0) {
717 return size;
718 }
719 int min = minorWasDefined ? min(minorRange.min, count) : 0;
720 return min(size, count - min);
721 }
722
Philip Milnef4748702011-06-09 18:30:32 -0700723 // install default indices for cells that don't define them
Philip Milne3f8956d2011-05-13 17:29:00 +0100724 private void validateLayoutParams() {
Philip Milnef6679c82011-09-18 11:36:57 -0700725 final boolean horizontal = (orientation == HORIZONTAL);
726 final Axis axis = horizontal ? horizontalAxis : verticalAxis;
Jim Miller782c04b2012-05-02 15:45:23 -0700727 final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
Philip Milne3f8956d2011-05-13 17:29:00 +0100728
Philip Milne899d5922011-07-21 11:39:37 -0700729 int major = 0;
730 int minor = 0;
731 int[] maxSizes = new int[count];
732
733 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milned7dd8902012-01-26 16:55:30 -0800734 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
Philip Milne899d5922011-07-21 11:39:37 -0700735
Philip Milnef6679c82011-09-18 11:36:57 -0700736 final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
737 final Interval majorRange = majorSpec.span;
738 final boolean majorWasDefined = majorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700739 final int majorSpan = majorRange.size();
740 if (majorWasDefined) {
741 major = majorRange.min;
Philip Milnef4748702011-06-09 18:30:32 -0700742 }
743
Philip Milnef6679c82011-09-18 11:36:57 -0700744 final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
745 final Interval minorRange = minorSpec.span;
746 final boolean minorWasDefined = minorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700747 final int minorSpan = clip(minorRange, minorWasDefined, count);
748 if (minorWasDefined) {
749 minor = minorRange.min;
750 }
Philip Milnef4748702011-06-09 18:30:32 -0700751
Philip Milne899d5922011-07-21 11:39:37 -0700752 if (count != 0) {
753 // Find suitable row/col values when at least one is undefined.
754 if (!majorWasDefined || !minorWasDefined) {
755 while (!fits(maxSizes, major, minor, minor + minorSpan)) {
756 if (minorWasDefined) {
757 major++;
758 } else {
759 if (minor + minorSpan <= count) {
760 minor++;
761 } else {
762 minor = 0;
763 major++;
764 }
Philip Milnef4748702011-06-09 18:30:32 -0700765 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100766 }
767 }
Philip Milne899d5922011-07-21 11:39:37 -0700768 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan);
Philip Milne3f8956d2011-05-13 17:29:00 +0100769 }
Philip Milne899d5922011-07-21 11:39:37 -0700770
771 if (horizontal) {
772 setCellGroup(lp, major, majorSpan, minor, minorSpan);
773 } else {
774 setCellGroup(lp, minor, minorSpan, major, majorSpan);
775 }
776
777 minor = minor + minorSpan;
778 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100779 }
780
781 private void invalidateStructure() {
Philip Milneedd69512012-03-14 17:21:33 -0700782 lastLayoutParamsHashCode = UNINITIALIZED_HASH;
Philip Milnef6679c82011-09-18 11:36:57 -0700783 horizontalAxis.invalidateStructure();
784 verticalAxis.invalidateStructure();
Philip Milne899d5922011-07-21 11:39:37 -0700785 // This can end up being done twice. Better twice than not at all.
Philip Milne3f8956d2011-05-13 17:29:00 +0100786 invalidateValues();
787 }
788
789 private void invalidateValues() {
Philip Milneaa616f32011-05-27 18:38:01 -0700790 // Need null check because requestLayout() is called in View's initializer,
791 // before we are set up.
Philip Milnef6679c82011-09-18 11:36:57 -0700792 if (horizontalAxis != null && verticalAxis != null) {
793 horizontalAxis.invalidateValues();
794 verticalAxis.invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -0700795 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100796 }
797
Philip Milned7dd8902012-01-26 16:55:30 -0800798 /** @hide */
799 @Override
800 protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) {
801 super.onSetLayoutParams(child, layoutParams);
Philip Milne0f57cea2012-05-12 09:34:25 -0700802
803 if (!checkLayoutParams(layoutParams)) {
804 handleInvalidParams("supplied LayoutParams are of the wrong type");
805 }
806
Philip Milned7dd8902012-01-26 16:55:30 -0800807 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100808 }
809
Philip Milnef6679c82011-09-18 11:36:57 -0700810 final LayoutParams getLayoutParams(View c) {
Philip Milned7dd8902012-01-26 16:55:30 -0800811 return (LayoutParams) c.getLayoutParams();
Philip Milne3f8956d2011-05-13 17:29:00 +0100812 }
813
Philip Milne0f57cea2012-05-12 09:34:25 -0700814 private static void handleInvalidParams(String msg) {
815 throw new IllegalArgumentException(msg + ". ");
816 }
817
818 private void checkLayoutParams(LayoutParams lp, boolean horizontal) {
819 String groupName = horizontal ? "column" : "row";
820 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
821 Interval span = spec.span;
822 if (span.min != UNDEFINED && span.min < 0) {
823 handleInvalidParams(groupName + " indices must be positive");
824 }
825 Axis axis = horizontal ? horizontalAxis : verticalAxis;
826 int count = axis.definedCount;
827 if (count != UNDEFINED) {
828 if (span.max > count) {
829 handleInvalidParams(groupName +
830 " indices (start + span) mustn't exceed the " + groupName + " count");
831 }
832 if (span.size() > count) {
833 handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count");
834 }
835 }
836 }
837
838 @Override
839 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
840 if (!(p instanceof LayoutParams)) {
841 return false;
842 }
843 LayoutParams lp = (LayoutParams) p;
844
845 checkLayoutParams(lp, true);
846 checkLayoutParams(lp, false);
847
848 return true;
849 }
850
Philip Milne3f8956d2011-05-13 17:29:00 +0100851 @Override
852 protected LayoutParams generateDefaultLayoutParams() {
853 return new LayoutParams();
854 }
855
856 @Override
857 public LayoutParams generateLayoutParams(AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -0700858 return new LayoutParams(getContext(), attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +0100859 }
860
861 @Override
862 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
863 return new LayoutParams(p);
864 }
865
866 // Draw grid
867
868 private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800869 if (isLayoutRtl()) {
870 int width = getWidth();
Philip Milne7b757812012-09-19 18:13:44 -0700871 graphics.drawLine(width - x1, y1, width - x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800872 } else {
Philip Milne7b757812012-09-19 18:13:44 -0700873 graphics.drawLine(x1, y1, x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800874 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100875 }
876
Philip Milne10ca24a2012-04-23 15:38:27 -0700877 /**
878 * @hide
879 */
880 @Override
Philip Milne7b757812012-09-19 18:13:44 -0700881 protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700882 // Apply defaults, so as to remove UNDEFINED values
883 LayoutParams lp = new LayoutParams();
884 for (int i = 0; i < getChildCount(); i++) {
885 View c = getChildAt(i);
886 lp.setMargins(
Philip Milne7b757812012-09-19 18:13:44 -0700887 getMargin1(c, true, true),
888 getMargin1(c, false, true),
889 getMargin1(c, true, false),
890 getMargin1(c, false, false));
891 lp.onDebugDraw(c, canvas, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700892 }
Philip Milneb5599762011-08-05 11:04:36 -0700893 }
894
Philip Milne10ca24a2012-04-23 15:38:27 -0700895 /**
896 * @hide
897 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100898 @Override
Philip Milne10ca24a2012-04-23 15:38:27 -0700899 protected void onDebugDraw(Canvas canvas) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700900 Paint paint = new Paint();
901 paint.setStyle(Paint.Style.STROKE);
902 paint.setColor(Color.argb(50, 255, 255, 255));
Philip Milne3f8956d2011-05-13 17:29:00 +0100903
Philip Milne7b757812012-09-19 18:13:44 -0700904 Insets insets = getOpticalInsets();
905
906 int top = getPaddingTop() + insets.top;
907 int left = getPaddingLeft() + insets.left;
908 int right = getWidth() - getPaddingRight() - insets.right;
909 int bottom = getHeight() - getPaddingBottom() - insets.bottom;
910
Philip Milne10ca24a2012-04-23 15:38:27 -0700911 int[] xs = horizontalAxis.locations;
912 if (xs != null) {
913 for (int i = 0, length = xs.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700914 int x = left + xs[i];
915 drawLine(canvas, x, top, x, bottom, paint);
Philip Milne3f8956d2011-05-13 17:29:00 +0100916 }
917 }
Philip Milne10ca24a2012-04-23 15:38:27 -0700918
919 int[] ys = verticalAxis.locations;
920 if (ys != null) {
921 for (int i = 0, length = ys.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700922 int y = top + ys[i];
923 drawLine(canvas, left, y, right, y, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700924 }
925 }
926
927 super.onDebugDraw(canvas);
Philip Milne3f8956d2011-05-13 17:29:00 +0100928 }
929
Philip Milne3f8956d2011-05-13 17:29:00 +0100930 // Add/remove
931
Philip Milnee7dda0b2011-07-19 10:35:56 -0700932 /**
933 * @hide
934 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100935 @Override
Philip Milnef51d91c2011-07-18 16:12:19 -0700936 protected void onViewAdded(View child) {
937 super.onViewAdded(child);
Philip Milne3f8956d2011-05-13 17:29:00 +0100938 invalidateStructure();
939 }
940
Philip Milnee7dda0b2011-07-19 10:35:56 -0700941 /**
942 * @hide
943 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100944 @Override
Philip Milnef51d91c2011-07-18 16:12:19 -0700945 protected void onViewRemoved(View child) {
946 super.onViewRemoved(child);
Philip Milneb0ce49b2011-07-15 15:39:07 -0700947 invalidateStructure();
948 }
949
Philip Milne350f0a62011-07-19 14:00:56 -0700950 /**
951 * We need to call invalidateStructure() when a child's GONE flag changes state.
952 * This implementation is a catch-all, invalidating on any change in the visibility flags.
953 *
954 * @hide
955 */
956 @Override
Chet Haase0d299362012-01-26 10:51:48 -0800957 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
958 super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
959 if (oldVisibility == GONE || newVisibility == GONE) {
Philip Milnea8416442013-02-25 10:49:39 -0800960 invalidateStructure();
Chet Haase0d299362012-01-26 10:51:48 -0800961 }
Philip Milne350f0a62011-07-19 14:00:56 -0700962 }
963
Philip Milned7dd8902012-01-26 16:55:30 -0800964 private int computeLayoutParamsHashCode() {
965 int result = 1;
966 for (int i = 0, N = getChildCount(); i < N; i++) {
967 View c = getChildAt(i);
968 if (c.getVisibility() == View.GONE) continue;
969 LayoutParams lp = (LayoutParams) c.getLayoutParams();
970 result = 31 * result + lp.hashCode();
971 }
972 return result;
Philip Milneb3a8c542011-06-20 16:02:59 -0700973 }
974
Philip Milneedd69512012-03-14 17:21:33 -0700975 private void consistencyCheck() {
976 if (lastLayoutParamsHashCode == UNINITIALIZED_HASH) {
977 validateLayoutParams();
978 lastLayoutParamsHashCode = computeLayoutParamsHashCode();
979 } else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
Philip Milne211d0332012-04-24 14:45:14 -0700980 printer.println("The fields of some layout parameters were modified in between "
981 + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
Philip Milneedd69512012-03-14 17:21:33 -0700982 invalidateStructure();
983 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -0800984 }
985 }
986
987 // Measurement
988
Philip Milnee0b85cd2013-04-12 14:38:34 -0700989 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -0700990 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
Philip Milneedd69512012-03-14 17:21:33 -0700991 int childWidth, int childHeight) {
Philip Milne4a145d72011-09-29 14:14:25 -0700992 int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -0700993 getTotalMargin(child, true), childWidth);
Philip Milne4a145d72011-09-29 14:14:25 -0700994 int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -0700995 getTotalMargin(child, false), childHeight);
Philip Milne4a145d72011-09-29 14:14:25 -0700996 child.measure(childWidthSpec, childHeightSpec);
Philip Milneb3a8c542011-06-20 16:02:59 -0700997 }
998
Philip Milnee0b85cd2013-04-12 14:38:34 -0700999 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -07001000 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001001 for (int i = 0, N = getChildCount(); i < N; i++) {
1002 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001003 if (c.getVisibility() == View.GONE) continue;
Philip Milne4a145d72011-09-29 14:14:25 -07001004 LayoutParams lp = getLayoutParams(c);
1005 if (firstPass) {
1006 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
1007 } else {
Philip Milneecab1172011-10-25 15:07:19 -07001008 boolean horizontal = (orientation == HORIZONTAL);
1009 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -07001010 if (spec.alignment == FILL) {
1011 Interval span = spec.span;
Philip Milneecab1172011-10-25 15:07:19 -07001012 Axis axis = horizontal ? horizontalAxis : verticalAxis;
Philip Milne4a145d72011-09-29 14:14:25 -07001013 int[] locations = axis.getLocations();
Philip Milneecab1172011-10-25 15:07:19 -07001014 int cellSize = locations[span.max] - locations[span.min];
1015 int viewSize = cellSize - getTotalMargin(c, horizontal);
1016 if (horizontal) {
1017 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);
Philip Milne4a145d72011-09-29 14:14:25 -07001018 } else {
Philip Milneecab1172011-10-25 15:07:19 -07001019 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);
Philip Milne4a145d72011-09-29 14:14:25 -07001020 }
1021 }
1022 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001023 }
1024 }
1025
Philip Milnee0b85cd2013-04-12 14:38:34 -07001026 static int adjust(int measureSpec, int delta) {
1027 return makeMeasureSpec(
1028 MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec));
1029 }
1030
Philip Milne3f8956d2011-05-13 17:29:00 +01001031 @Override
1032 protected void onMeasure(int widthSpec, int heightSpec) {
Philip Milneedd69512012-03-14 17:21:33 -07001033 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001034
Philip Milne4a145d72011-09-29 14:14:25 -07001035 /** If we have been called by {@link View#measure(int, int)}, one of width or height
1036 * is likely to have changed. We must invalidate if so. */
1037 invalidateValues();
Philip Milne3f8956d2011-05-13 17:29:00 +01001038
Philip Milnee0b85cd2013-04-12 14:38:34 -07001039 int hPadding = getPaddingLeft() + getPaddingRight();
1040 int vPadding = getPaddingTop() + getPaddingBottom();
Philip Milne3f8956d2011-05-13 17:29:00 +01001041
Philip Milnee0b85cd2013-04-12 14:38:34 -07001042 int widthSpecSansPadding = adjust( widthSpec, -hPadding);
1043 int heightSpecSansPadding = adjust(heightSpec, -vPadding);
1044
1045 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);
1046
1047 int widthSansPadding;
1048 int heightSansPadding;
Philip Milne4a145d72011-09-29 14:14:25 -07001049
1050 // Use the orientation property to decide which axis should be laid out first.
1051 if (orientation == HORIZONTAL) {
Philip Milnee0b85cd2013-04-12 14:38:34 -07001052 widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
1053 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
1054 heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001055 } else {
Philip Milnee0b85cd2013-04-12 14:38:34 -07001056 heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
1057 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
1058 widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001059 }
1060
Philip Milnee0b85cd2013-04-12 14:38:34 -07001061 int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth());
1062 int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight());
Philip Milne09e2d4d2011-06-20 10:28:18 -07001063
Philip Milne3f8956d2011-05-13 17:29:00 +01001064 setMeasuredDimension(
Philip Milnee0b85cd2013-04-12 14:38:34 -07001065 resolveSizeAndState(measuredWidth, widthSpec, 0),
Philip Milne09e2d4d2011-06-20 10:28:18 -07001066 resolveSizeAndState(measuredHeight, heightSpec, 0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001067 }
1068
Philip Milne48b55242011-06-29 11:09:45 -07001069 private int getMeasurement(View c, boolean horizontal) {
Philip Milne7b757812012-09-19 18:13:44 -07001070 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
Philip Milneaa616f32011-05-27 18:38:01 -07001071 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001072
Philip Milnef6679c82011-09-18 11:36:57 -07001073 final int getMeasurementIncludingMargin(View c, boolean horizontal) {
Philip Milned7dd8902012-01-26 16:55:30 -08001074 if (c.getVisibility() == View.GONE) {
Philip Milne5125e212011-07-21 11:39:37 -07001075 return 0;
1076 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001077 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
Philip Milne3f8956d2011-05-13 17:29:00 +01001078 }
1079
Philip Milne3f8956d2011-05-13 17:29:00 +01001080 @Override
Philip Milneaa616f32011-05-27 18:38:01 -07001081 public void requestLayout() {
1082 super.requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +01001083 invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -07001084 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001085
Philip Milnef6679c82011-09-18 11:36:57 -07001086 final Alignment getAlignment(Alignment alignment, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -07001087 return (alignment != UNDEFINED_ALIGNMENT) ? alignment :
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08001088 (horizontal ? START : BASELINE);
Philip Milne5125e212011-07-21 11:39:37 -07001089 }
1090
Philip Milneaa616f32011-05-27 18:38:01 -07001091 // Layout container
1092
1093 /**
1094 * {@inheritDoc}
1095 */
1096 /*
1097 The layout operation is implemented by delegating the heavy lifting to the
1098 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
1099 Together they compute the locations of the vertical and horizontal lines of
1100 the grid (respectively!).
1101
1102 This method is then left with the simpler task of applying margins, gravity
1103 and sizing to each child view and then placing it in its cell.
1104 */
1105 @Override
Philip Milne09e2d4d2011-06-20 10:28:18 -07001106 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Philip Milneedd69512012-03-14 17:21:33 -07001107 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001108
Philip Milne09e2d4d2011-06-20 10:28:18 -07001109 int targetWidth = right - left;
1110 int targetHeight = bottom - top;
Philip Milne3f8956d2011-05-13 17:29:00 +01001111
1112 int paddingLeft = getPaddingLeft();
1113 int paddingTop = getPaddingTop();
1114 int paddingRight = getPaddingRight();
1115 int paddingBottom = getPaddingBottom();
1116
Philip Milnef6679c82011-09-18 11:36:57 -07001117 horizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
1118 verticalAxis.layout(targetHeight - paddingTop - paddingBottom);
Philip Milne3f8956d2011-05-13 17:29:00 +01001119
Philip Milnef6679c82011-09-18 11:36:57 -07001120 int[] hLocations = horizontalAxis.getLocations();
1121 int[] vLocations = verticalAxis.getLocations();
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001122
Philip Milneb3a8c542011-06-20 16:02:59 -07001123 for (int i = 0, N = getChildCount(); i < N; i++) {
1124 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001125 if (c.getVisibility() == View.GONE) continue;
Philip Milneb3a8c542011-06-20 16:02:59 -07001126 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001127 Spec columnSpec = lp.columnSpec;
1128 Spec rowSpec = lp.rowSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001129
Philip Milne93cd6a62011-07-12 14:49:45 -07001130 Interval colSpan = columnSpec.span;
1131 Interval rowSpan = rowSpec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001132
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001133 int x1 = hLocations[colSpan.min];
1134 int y1 = vLocations[rowSpan.min];
Philip Milneaa616f32011-05-27 18:38:01 -07001135
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001136 int x2 = hLocations[colSpan.max];
1137 int y2 = vLocations[rowSpan.max];
Philip Milne3f8956d2011-05-13 17:29:00 +01001138
1139 int cellWidth = x2 - x1;
1140 int cellHeight = y2 - y1;
1141
Philip Milne48b55242011-06-29 11:09:45 -07001142 int pWidth = getMeasurement(c, true);
1143 int pHeight = getMeasurement(c, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001144
Philip Milne5125e212011-07-21 11:39:37 -07001145 Alignment hAlign = getAlignment(columnSpec.alignment, true);
1146 Alignment vAlign = getAlignment(rowSpec.alignment, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001147
Philip Milne6216e872012-02-16 17:15:50 -08001148 Bounds boundsX = horizontalAxis.getGroupBounds().getValue(i);
1149 Bounds boundsY = verticalAxis.getGroupBounds().getValue(i);
Philip Milne7fd94872011-06-07 20:14:17 -07001150
1151 // Gravity offsets: the location of the alignment group relative to its cell group.
Philip Milne6216e872012-02-16 17:15:50 -08001152 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true));
1153 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true));
Philip Milne7fd94872011-06-07 20:14:17 -07001154
Philip Milne6216e872012-02-16 17:15:50 -08001155 int leftMargin = getMargin(c, true, true);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001156 int topMargin = getMargin(c, false, true);
Philip Milne6216e872012-02-16 17:15:50 -08001157 int rightMargin = getMargin(c, true, false);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001158 int bottomMargin = getMargin(c, false, false);
Philip Milne7fd94872011-06-07 20:14:17 -07001159
Philip Milne1557fd72012-04-04 23:41:34 -07001160 int sumMarginsX = leftMargin + rightMargin;
1161 int sumMarginsY = topMargin + bottomMargin;
Philip Milne7fd94872011-06-07 20:14:17 -07001162
Philip Milne1557fd72012-04-04 23:41:34 -07001163 // Alignment offsets: the location of the view relative to its alignment group.
1164 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
1165 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
1166
1167 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
1168 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
Philip Milne7fd94872011-06-07 20:14:17 -07001169
Philip Milne6216e872012-02-16 17:15:50 -08001170 int dx = x1 + gravityOffsetX + alignmentOffsetX;
Philip Milne3f8956d2011-05-13 17:29:00 +01001171
Philip Milne6216e872012-02-16 17:15:50 -08001172 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx :
1173 targetWidth - width - paddingRight - rightMargin - dx;
1174 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
Philip Milne3f8956d2011-05-13 17:29:00 +01001175
Philip Milne899d5922011-07-21 11:39:37 -07001176 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
1177 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
1178 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001179 c.layout(cx, cy, cx + width, cy + height);
Philip Milne3f8956d2011-05-13 17:29:00 +01001180 }
1181 }
1182
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001183 @Override
1184 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1185 super.onInitializeAccessibilityEvent(event);
1186 event.setClassName(GridLayout.class.getName());
1187 }
1188
1189 @Override
1190 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1191 super.onInitializeAccessibilityNodeInfo(info);
1192 info.setClassName(GridLayout.class.getName());
1193 }
1194
Philip Milne3f8956d2011-05-13 17:29:00 +01001195 // Inner classes
1196
Philip Milneaa616f32011-05-27 18:38:01 -07001197 /*
1198 This internal class houses the algorithm for computing the locations of grid lines;
1199 along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
1200 distinguished by the "horizontal" flag which is true for the horizontal axis and false
1201 for the vertical one.
1202 */
Philip Milnef6679c82011-09-18 11:36:57 -07001203 final class Axis {
Philip Milne48b55242011-06-29 11:09:45 -07001204 private static final int NEW = 0;
Philip Milne3f8956d2011-05-13 17:29:00 +01001205 private static final int PENDING = 1;
1206 private static final int COMPLETE = 2;
1207
1208 public final boolean horizontal;
1209
Philip Milnef6679c82011-09-18 11:36:57 -07001210 public int definedCount = UNDEFINED;
Philip Milne4a145d72011-09-29 14:14:25 -07001211 private int maxIndex = UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001212
Philip Milne93cd6a62011-07-12 14:49:45 -07001213 PackedMap<Spec, Bounds> groupBounds;
Philip Milne3f8956d2011-05-13 17:29:00 +01001214 public boolean groupBoundsValid = false;
1215
Philip Milne48b55242011-06-29 11:09:45 -07001216 PackedMap<Interval, MutableInt> forwardLinks;
1217 public boolean forwardLinksValid = false;
1218
1219 PackedMap<Interval, MutableInt> backwardLinks;
1220 public boolean backwardLinksValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001221
Philip Milne3f8956d2011-05-13 17:29:00 +01001222 public int[] leadingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001223 public boolean leadingMarginsValid = false;
1224
Philip Milne3f8956d2011-05-13 17:29:00 +01001225 public int[] trailingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001226 public boolean trailingMarginsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001227
1228 public Arc[] arcs;
1229 public boolean arcsValid = false;
1230
Philip Milneaa616f32011-05-27 18:38:01 -07001231 public int[] locations;
Philip Milne48b55242011-06-29 11:09:45 -07001232 public boolean locationsValid = false;
Philip Milneaa616f32011-05-27 18:38:01 -07001233
Philip Milnef6679c82011-09-18 11:36:57 -07001234 boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001235
Philip Milne48b55242011-06-29 11:09:45 -07001236 private MutableInt parentMin = new MutableInt(0);
1237 private MutableInt parentMax = new MutableInt(-MAX_SIZE);
1238
Philip Milne3f8956d2011-05-13 17:29:00 +01001239 private Axis(boolean horizontal) {
1240 this.horizontal = horizontal;
1241 }
1242
Philip Milne4a145d72011-09-29 14:14:25 -07001243 private int calculateMaxIndex() {
1244 // the number Integer.MIN_VALUE + 1 comes up in undefined cells
1245 int result = -1;
Philip Milneb3a8c542011-06-20 16:02:59 -07001246 for (int i = 0, N = getChildCount(); i < N; i++) {
1247 View c = getChildAt(i);
Philip Milneb3a8c542011-06-20 16:02:59 -07001248 LayoutParams params = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001249 Spec spec = horizontal ? params.columnSpec : params.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -07001250 Interval span = spec.span;
1251 result = max(result, span.min);
1252 result = max(result, span.max);
Philip Milne0f57cea2012-05-12 09:34:25 -07001253 result = max(result, span.size());
Philip Milne3f8956d2011-05-13 17:29:00 +01001254 }
Philip Milne4a145d72011-09-29 14:14:25 -07001255 return result == -1 ? UNDEFINED : result;
Philip Milne3f8956d2011-05-13 17:29:00 +01001256 }
1257
Philip Milne4a145d72011-09-29 14:14:25 -07001258 private int getMaxIndex() {
1259 if (maxIndex == UNDEFINED) {
1260 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children
Philip Milne3f8956d2011-05-13 17:29:00 +01001261 }
Philip Milne4a145d72011-09-29 14:14:25 -07001262 return maxIndex;
Philip Milnef6679c82011-09-18 11:36:57 -07001263 }
1264
1265 public int getCount() {
Philip Milne4a145d72011-09-29 14:14:25 -07001266 return max(definedCount, getMaxIndex());
Philip Milne3f8956d2011-05-13 17:29:00 +01001267 }
1268
1269 public void setCount(int count) {
Philip Milne0f57cea2012-05-12 09:34:25 -07001270 if (count != UNDEFINED && count < getMaxIndex()) {
1271 handleInvalidParams((horizontal ? "column" : "row") +
1272 "Count must be greater than or equal to the maximum of all grid indices " +
1273 "(and spans) defined in the LayoutParams of each child");
1274 }
Philip Milnef6679c82011-09-18 11:36:57 -07001275 this.definedCount = count;
Philip Milne3f8956d2011-05-13 17:29:00 +01001276 }
1277
1278 public boolean isOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -07001279 return orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001280 }
1281
1282 public void setOrderPreserved(boolean orderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -07001283 this.orderPreserved = orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001284 invalidateStructure();
1285 }
1286
Philip Milne93cd6a62011-07-12 14:49:45 -07001287 private PackedMap<Spec, Bounds> createGroupBounds() {
1288 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class);
Philip Milne48b55242011-06-29 11:09:45 -07001289 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001290 View c = getChildAt(i);
Philip Milnea8416442013-02-25 10:49:39 -08001291 // we must include views that are GONE here, see introductory javadoc
Philip Milne5125e212011-07-21 11:39:37 -07001292 LayoutParams lp = getLayoutParams(c);
1293 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1294 Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds();
1295 assoc.put(spec, bounds);
Philip Milne3f8956d2011-05-13 17:29:00 +01001296 }
Philip Milne48b55242011-06-29 11:09:45 -07001297 return assoc.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001298 }
1299
1300 private void computeGroupBounds() {
Philip Milneb3a8c542011-06-20 16:02:59 -07001301 Bounds[] values = groupBounds.values;
1302 for (int i = 0; i < values.length; i++) {
1303 values[i].reset();
Philip Milne3f8956d2011-05-13 17:29:00 +01001304 }
Philip Milneaa616f32011-05-27 18:38:01 -07001305 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001306 View c = getChildAt(i);
Philip Milnea8416442013-02-25 10:49:39 -08001307 // we must include views that are GONE here, see introductory javadoc
Philip Milne3f8956d2011-05-13 17:29:00 +01001308 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001309 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Philip Milne1557fd72012-04-04 23:41:34 -07001310 groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
Philip Milne3f8956d2011-05-13 17:29:00 +01001311 }
1312 }
1313
Philip Milnef6679c82011-09-18 11:36:57 -07001314 public PackedMap<Spec, Bounds> getGroupBounds() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001315 if (groupBounds == null) {
1316 groupBounds = createGroupBounds();
1317 }
1318 if (!groupBoundsValid) {
1319 computeGroupBounds();
1320 groupBoundsValid = true;
1321 }
1322 return groupBounds;
1323 }
1324
1325 // Add values computed by alignment - taking the max of all alignments in each span
Philip Milne48b55242011-06-29 11:09:45 -07001326 private PackedMap<Interval, MutableInt> createLinks(boolean min) {
1327 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class);
Philip Milne93cd6a62011-07-12 14:49:45 -07001328 Spec[] keys = getGroupBounds().keys;
Philip Milne48b55242011-06-29 11:09:45 -07001329 for (int i = 0, N = keys.length; i < N; i++) {
1330 Interval span = min ? keys[i].span : keys[i].span.inverse();
1331 result.put(span, new MutableInt());
Philip Milne3f8956d2011-05-13 17:29:00 +01001332 }
Philip Milne48b55242011-06-29 11:09:45 -07001333 return result.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001334 }
1335
Philip Milne48b55242011-06-29 11:09:45 -07001336 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) {
1337 MutableInt[] spans = links.values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001338 for (int i = 0; i < spans.length; i++) {
1339 spans[i].reset();
1340 }
1341
Philip Milne5d1a9842011-07-07 11:47:08 -07001342 // Use getter to trigger a re-evaluation
Philip Milne48b55242011-06-29 11:09:45 -07001343 Bounds[] bounds = getGroupBounds().values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001344 for (int i = 0; i < bounds.length; i++) {
Philip Milne48b55242011-06-29 11:09:45 -07001345 int size = bounds[i].size(min);
Philip Milne48b55242011-06-29 11:09:45 -07001346 MutableInt valueHolder = links.getValue(i);
Philip Milne5125e212011-07-21 11:39:37 -07001347 // this effectively takes the max() of the minima and the min() of the maxima
1348 valueHolder.value = max(valueHolder.value, min ? size : -size);
Philip Milne3f8956d2011-05-13 17:29:00 +01001349 }
1350 }
1351
Philip Milne48b55242011-06-29 11:09:45 -07001352 private PackedMap<Interval, MutableInt> getForwardLinks() {
1353 if (forwardLinks == null) {
1354 forwardLinks = createLinks(true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001355 }
Philip Milne48b55242011-06-29 11:09:45 -07001356 if (!forwardLinksValid) {
1357 computeLinks(forwardLinks, true);
1358 forwardLinksValid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01001359 }
Philip Milne48b55242011-06-29 11:09:45 -07001360 return forwardLinks;
Philip Milne3f8956d2011-05-13 17:29:00 +01001361 }
1362
Philip Milne48b55242011-06-29 11:09:45 -07001363 private PackedMap<Interval, MutableInt> getBackwardLinks() {
1364 if (backwardLinks == null) {
1365 backwardLinks = createLinks(false);
1366 }
1367 if (!backwardLinksValid) {
1368 computeLinks(backwardLinks, false);
1369 backwardLinksValid = true;
1370 }
1371 return backwardLinks;
1372 }
1373
1374 private void include(List<Arc> arcs, Interval key, MutableInt size,
Philip Milneedd69512012-03-14 17:21:33 -07001375 boolean ignoreIfAlreadyPresent) {
Philip Milne48b55242011-06-29 11:09:45 -07001376 /*
1377 Remove self referential links.
1378 These appear:
1379 . as parental constraints when GridLayout has no children
1380 . when components have been marked as GONE
1381 */
1382 if (key.size() == 0) {
1383 return;
1384 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001385 // this bit below should really be computed outside here -
Philip Milne48b55242011-06-29 11:09:45 -07001386 // its just to stop default (row/col > 0) constraints obliterating valid entries
1387 if (ignoreIfAlreadyPresent) {
1388 for (Arc arc : arcs) {
1389 Interval span = arc.span;
1390 if (span.equals(key)) {
1391 return;
1392 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001393 }
1394 }
1395 arcs.add(new Arc(key, size));
1396 }
1397
Philip Milne48b55242011-06-29 11:09:45 -07001398 private void include(List<Arc> arcs, Interval key, MutableInt size) {
1399 include(arcs, key, size, true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001400 }
1401
Philip Milneaa616f32011-05-27 18:38:01 -07001402 // Group arcs by their first vertex, returning an array of arrays.
Philip Milne3f8956d2011-05-13 17:29:00 +01001403 // This is linear in the number of arcs.
Philip Milnef6679c82011-09-18 11:36:57 -07001404 Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
Philip Milne48b55242011-06-29 11:09:45 -07001405 int N = getCount() + 1; // the number of vertices
Philip Milne3f8956d2011-05-13 17:29:00 +01001406 Arc[][] result = new Arc[N][];
1407 int[] sizes = new int[N];
1408 for (Arc arc : arcs) {
1409 sizes[arc.span.min]++;
Philip Milne5d1a9842011-07-07 11:47:08 -07001410 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001411 for (int i = 0; i < sizes.length; i++) {
1412 result[i] = new Arc[sizes[i]];
1413 }
1414 // reuse the sizes array to hold the current last elements as we insert each arc
1415 Arrays.fill(sizes, 0);
1416 for (Arc arc : arcs) {
1417 int i = arc.span.min;
1418 result[i][sizes[i]++] = arc;
1419 }
1420
1421 return result;
1422 }
1423
Philip Milne48b55242011-06-29 11:09:45 -07001424 private Arc[] topologicalSort(final Arc[] arcs) {
1425 return new Object() {
1426 Arc[] result = new Arc[arcs.length];
1427 int cursor = result.length - 1;
1428 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001429 int[] visited = new int[getCount() + 1];
1430
Philip Milne48b55242011-06-29 11:09:45 -07001431 void walk(int loc) {
1432 switch (visited[loc]) {
1433 case NEW: {
1434 visited[loc] = PENDING;
1435 for (Arc arc : arcsByVertex[loc]) {
1436 walk(arc.span.max);
1437 result[cursor--] = arc;
Philip Milne3f8956d2011-05-13 17:29:00 +01001438 }
Philip Milne48b55242011-06-29 11:09:45 -07001439 visited[loc] = COMPLETE;
1440 break;
Philip Milne3f8956d2011-05-13 17:29:00 +01001441 }
Philip Milne48b55242011-06-29 11:09:45 -07001442 case PENDING: {
Philip Milneb65408f2012-05-21 10:44:46 -07001443 // le singe est dans l'arbre
Philip Milne48b55242011-06-29 11:09:45 -07001444 assert false;
1445 break;
1446 }
1447 case COMPLETE: {
1448 break;
1449 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001450 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001451 }
Philip Milne48b55242011-06-29 11:09:45 -07001452
1453 Arc[] sort() {
1454 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) {
1455 walk(loc);
1456 }
1457 assert cursor == -1;
1458 return result;
1459 }
1460 }.sort();
1461 }
1462
1463 private Arc[] topologicalSort(List<Arc> arcs) {
1464 return topologicalSort(arcs.toArray(new Arc[arcs.size()]));
Philip Milne3f8956d2011-05-13 17:29:00 +01001465 }
1466
Philip Milne48b55242011-06-29 11:09:45 -07001467 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) {
1468 for (int i = 0; i < links.keys.length; i++) {
1469 Interval key = links.keys[i];
1470 include(result, key, links.values[i], false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001471 }
Philip Milne48b55242011-06-29 11:09:45 -07001472 }
1473
1474 private Arc[] createArcs() {
1475 List<Arc> mins = new ArrayList<Arc>();
1476 List<Arc> maxs = new ArrayList<Arc>();
1477
1478 // Add the minimum values from the components.
1479 addComponentSizes(mins, getForwardLinks());
1480 // Add the maximum values from the components.
1481 addComponentSizes(maxs, getBackwardLinks());
Philip Milne3f8956d2011-05-13 17:29:00 +01001482
Philip Milne48b55242011-06-29 11:09:45 -07001483 // Add ordering constraints to prevent row/col sizes from going negative
Philip Milnef6679c82011-09-18 11:36:57 -07001484 if (orderPreserved) {
Philip Milne48b55242011-06-29 11:09:45 -07001485 // Add a constraint for every row/col
Philip Milne3f8956d2011-05-13 17:29:00 +01001486 for (int i = 0; i < getCount(); i++) {
Philip Milne899d5922011-07-21 11:39:37 -07001487 include(mins, new Interval(i, i + 1), new MutableInt(0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001488 }
1489 }
Philip Milne48b55242011-06-29 11:09:45 -07001490
1491 // Add the container constraints. Use the version of include that allows
1492 // duplicate entries in case a child spans the entire grid.
1493 int N = getCount();
1494 include(mins, new Interval(0, N), parentMin, false);
1495 include(maxs, new Interval(N, 0), parentMax, false);
1496
1497 // Sort
1498 Arc[] sMins = topologicalSort(mins);
1499 Arc[] sMaxs = topologicalSort(maxs);
1500
1501 return append(sMins, sMaxs);
1502 }
1503
1504 private void computeArcs() {
1505 // getting the links validates the values that are shared by the arc list
1506 getForwardLinks();
1507 getBackwardLinks();
Philip Milne3f8956d2011-05-13 17:29:00 +01001508 }
1509
Philip Milneaa616f32011-05-27 18:38:01 -07001510 public Arc[] getArcs() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001511 if (arcs == null) {
Philip Milneaa616f32011-05-27 18:38:01 -07001512 arcs = createArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001513 }
1514 if (!arcsValid) {
Philip Milne48b55242011-06-29 11:09:45 -07001515 computeArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001516 arcsValid = true;
1517 }
1518 return arcs;
1519 }
1520
Philip Milneaa616f32011-05-27 18:38:01 -07001521 private boolean relax(int[] locations, Arc entry) {
Philip Milne48b55242011-06-29 11:09:45 -07001522 if (!entry.valid) {
1523 return false;
1524 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001525 Interval span = entry.span;
1526 int u = span.min;
1527 int v = span.max;
1528 int value = entry.value.value;
1529 int candidate = locations[u] + value;
Philip Milneaa616f32011-05-27 18:38:01 -07001530 if (candidate > locations[v]) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001531 locations[v] = candidate;
1532 return true;
1533 }
1534 return false;
1535 }
1536
Philip Milnef6679c82011-09-18 11:36:57 -07001537 private void init(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001538 Arrays.fill(locations, 0);
Philip Milnef6679c82011-09-18 11:36:57 -07001539 }
1540
1541 private String arcsToString(List<Arc> arcs) {
Philip Milne4a145d72011-09-29 14:14:25 -07001542 String var = horizontal ? "x" : "y";
Philip Milnef6679c82011-09-18 11:36:57 -07001543 StringBuilder result = new StringBuilder();
Philip Milne4a145d72011-09-29 14:14:25 -07001544 boolean first = true;
1545 for (Arc arc : arcs) {
1546 if (first) {
1547 first = false;
Philip Milnef6679c82011-09-18 11:36:57 -07001548 } else {
Philip Milne4a145d72011-09-29 14:14:25 -07001549 result = result.append(", ");
Philip Milnef6679c82011-09-18 11:36:57 -07001550 }
1551 int src = arc.span.min;
1552 int dst = arc.span.max;
1553 int value = arc.value.value;
1554 result.append((src < dst) ?
Philip Milneedd69512012-03-14 17:21:33 -07001555 var + dst + "-" + var + src + ">=" + value :
1556 var + src + "-" + var + dst + "<=" + -value);
Philip Milnef6679c82011-09-18 11:36:57 -07001557
1558 }
1559 return result.toString();
1560 }
1561
1562 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) {
1563 List<Arc> culprits = new ArrayList<Arc>();
1564 List<Arc> removed = new ArrayList<Arc>();
1565 for (int c = 0; c < arcs.length; c++) {
1566 Arc arc = arcs[c];
1567 if (culprits0[c]) {
1568 culprits.add(arc);
1569 }
1570 if (!arc.valid) {
1571 removed.add(arc);
1572 }
1573 }
Philip Milne211d0332012-04-24 14:45:14 -07001574 printer.println(axisName + " constraints: " + arcsToString(culprits) +
1575 " are inconsistent; permanently removing: " + arcsToString(removed) + ". ");
Philip Milnef6679c82011-09-18 11:36:57 -07001576 }
1577
Philip Milneaa616f32011-05-27 18:38:01 -07001578 /*
1579 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
1580
1581 GridLayout converts its requirements into a system of linear constraints of the
1582 form:
1583
1584 x[i] - x[j] < a[k]
1585
1586 Where the x[i] are variables and the a[k] are constants.
1587
1588 For example, if the variables were instead labeled x, y, z we might have:
1589
1590 x - y < 17
1591 y - z < 23
1592 z - x < 42
1593
1594 This is a special case of the Linear Programming problem that is, in turn,
1595 equivalent to the single-source shortest paths problem on a digraph, for
1596 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
Philip Milneaa616f32011-05-27 18:38:01 -07001597 */
Philip Milne48b55242011-06-29 11:09:45 -07001598 private void solve(Arc[] arcs, int[] locations) {
Philip Milnef6679c82011-09-18 11:36:57 -07001599 String axisName = horizontal ? "horizontal" : "vertical";
Philip Milne3f8956d2011-05-13 17:29:00 +01001600 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
Philip Milnef6679c82011-09-18 11:36:57 -07001601 boolean[] originalCulprits = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001602
Philip Milnef6679c82011-09-18 11:36:57 -07001603 for (int p = 0; p < arcs.length; p++) {
1604 init(locations);
1605
1606 // We take one extra pass over traditional Bellman-Ford (and omit their final step)
1607 for (int i = 0; i < N; i++) {
1608 boolean changed = false;
1609 for (int j = 0, length = arcs.length; j < length; j++) {
1610 changed |= relax(locations, arcs[j]);
Philip Milne3f8956d2011-05-13 17:29:00 +01001611 }
Philip Milnef6679c82011-09-18 11:36:57 -07001612 if (!changed) {
1613 if (originalCulprits != null) {
1614 logError(axisName, arcs, originalCulprits);
1615 }
Philip Milnef6679c82011-09-18 11:36:57 -07001616 return;
Philip Milne48b55242011-06-29 11:09:45 -07001617 }
Philip Milnef6679c82011-09-18 11:36:57 -07001618 }
1619
1620 boolean[] culprits = new boolean[arcs.length];
1621 for (int i = 0; i < N; i++) {
1622 for (int j = 0, length = arcs.length; j < length; j++) {
1623 culprits[j] |= relax(locations, arcs[j]);
1624 }
1625 }
1626
1627 if (p == 0) {
1628 originalCulprits = culprits;
1629 }
1630
1631 for (int i = 0; i < arcs.length; i++) {
1632 if (culprits[i]) {
1633 Arc arc = arcs[i];
1634 // Only remove max values, min values alone cannot be inconsistent
1635 if (arc.span.min < arc.span.max) {
1636 continue;
1637 }
1638 arc.valid = false;
1639 break;
1640 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001641 }
1642 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001643 }
1644
Philip Milneaa616f32011-05-27 18:38:01 -07001645 private void computeMargins(boolean leading) {
1646 int[] margins = leading ? leadingMargins : trailingMargins;
Philip Milneb3a8c542011-06-20 16:02:59 -07001647 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001648 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001649 if (c.getVisibility() == View.GONE) continue;
Philip Milne3f8956d2011-05-13 17:29:00 +01001650 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001651 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1652 Interval span = spec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001653 int index = leading ? span.min : span.max;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001654 margins[index] = max(margins[index], getMargin1(c, horizontal, leading));
Philip Milne3f8956d2011-05-13 17:29:00 +01001655 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001656 }
1657
Philip Milnef6679c82011-09-18 11:36:57 -07001658 // External entry points
1659
1660 public int[] getLeadingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001661 if (leadingMargins == null) {
1662 leadingMargins = new int[getCount() + 1];
1663 }
1664 if (!leadingMarginsValid) {
1665 computeMargins(true);
1666 leadingMarginsValid = true;
1667 }
1668 return leadingMargins;
1669 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001670
Philip Milnef6679c82011-09-18 11:36:57 -07001671 public int[] getTrailingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001672 if (trailingMargins == null) {
1673 trailingMargins = new int[getCount() + 1];
1674 }
1675 if (!trailingMarginsValid) {
1676 computeMargins(false);
1677 trailingMarginsValid = true;
1678 }
1679 return trailingMargins;
1680 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001681
Philip Milne48b55242011-06-29 11:09:45 -07001682 private void computeLocations(int[] a) {
Philip Milnef6679c82011-09-18 11:36:57 -07001683 solve(getArcs(), a);
Philip Milne4a145d72011-09-29 14:14:25 -07001684 if (!orderPreserved) {
1685 // Solve returns the smallest solution to the constraint system for which all
1686 // values are positive. One value is therefore zero - though if the row/col
1687 // order is not preserved this may not be the first vertex. For consistency,
1688 // translate all the values so that they measure the distance from a[0]; the
1689 // leading edge of the parent. After this transformation some values may be
1690 // negative.
1691 int a0 = a[0];
1692 for (int i = 0, N = a.length; i < N; i++) {
1693 a[i] = a[i] - a0;
1694 }
1695 }
Philip Milneaa616f32011-05-27 18:38:01 -07001696 }
1697
Philip Milnef6679c82011-09-18 11:36:57 -07001698 public int[] getLocations() {
Philip Milneaa616f32011-05-27 18:38:01 -07001699 if (locations == null) {
1700 int N = getCount() + 1;
1701 locations = new int[N];
1702 }
Philip Milne48b55242011-06-29 11:09:45 -07001703 if (!locationsValid) {
1704 computeLocations(locations);
1705 locationsValid = true;
1706 }
Philip Milneaa616f32011-05-27 18:38:01 -07001707 return locations;
1708 }
1709
Philip Milne3f8956d2011-05-13 17:29:00 +01001710 private int size(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001711 // The parental edges are attached to vertices 0 and N - even when order is not
1712 // being preserved and other vertices fall outside this range. Measure the distance
1713 // between vertices 0 and N, assuming that locations[0] = 0.
1714 return locations[getCount()];
Philip Milne3f8956d2011-05-13 17:29:00 +01001715 }
1716
Philip Milne48b55242011-06-29 11:09:45 -07001717 private void setParentConstraints(int min, int max) {
1718 parentMin.value = min;
1719 parentMax.value = -max;
1720 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001721 }
1722
Philip Milne48b55242011-06-29 11:09:45 -07001723 private int getMeasure(int min, int max) {
1724 setParentConstraints(min, max);
1725 return size(getLocations());
1726 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001727
Philip Milnef6679c82011-09-18 11:36:57 -07001728 public int getMeasure(int measureSpec) {
Philip Milne48b55242011-06-29 11:09:45 -07001729 int mode = MeasureSpec.getMode(measureSpec);
1730 int size = MeasureSpec.getSize(measureSpec);
1731 switch (mode) {
1732 case MeasureSpec.UNSPECIFIED: {
Philip Milne93cd6a62011-07-12 14:49:45 -07001733 return getMeasure(0, MAX_SIZE);
Philip Milne48b55242011-06-29 11:09:45 -07001734 }
1735 case MeasureSpec.EXACTLY: {
1736 return getMeasure(size, size);
1737 }
1738 case MeasureSpec.AT_MOST: {
1739 return getMeasure(0, size);
1740 }
1741 default: {
1742 assert false;
1743 return 0;
1744 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001745 }
Philip Milne48b55242011-06-29 11:09:45 -07001746 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001747
Philip Milnef6679c82011-09-18 11:36:57 -07001748 public void layout(int size) {
Philip Milne48b55242011-06-29 11:09:45 -07001749 setParentConstraints(size, size);
1750 getLocations();
Philip Milne3f8956d2011-05-13 17:29:00 +01001751 }
1752
Philip Milnef6679c82011-09-18 11:36:57 -07001753 public void invalidateStructure() {
Philip Milne4a145d72011-09-29 14:14:25 -07001754 maxIndex = UNDEFINED;
Philip Milneaa616f32011-05-27 18:38:01 -07001755
Philip Milne3f8956d2011-05-13 17:29:00 +01001756 groupBounds = null;
Philip Milne48b55242011-06-29 11:09:45 -07001757 forwardLinks = null;
1758 backwardLinks = null;
1759
Philip Milneaa616f32011-05-27 18:38:01 -07001760 leadingMargins = null;
1761 trailingMargins = null;
Philip Milnec9885f62011-06-15 17:07:35 -07001762 arcs = null;
Philip Milne48b55242011-06-29 11:09:45 -07001763
Philip Milneaa616f32011-05-27 18:38:01 -07001764 locations = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001765
1766 invalidateValues();
1767 }
1768
Philip Milnef6679c82011-09-18 11:36:57 -07001769 public void invalidateValues() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001770 groupBoundsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001771 forwardLinksValid = false;
1772 backwardLinksValid = false;
1773
Philip Milneaa616f32011-05-27 18:38:01 -07001774 leadingMarginsValid = false;
1775 trailingMarginsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001776 arcsValid = false;
1777
1778 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001779 }
1780 }
1781
1782 /**
1783 * Layout information associated with each of the children of a GridLayout.
1784 * <p>
1785 * GridLayout supports both row and column spanning and arbitrary forms of alignment within
1786 * each cell group. The fundamental parameters associated with each cell group are
1787 * gathered into their vertical and horizontal components and stored
Philip Milne93cd6a62011-07-12 14:49:45 -07001788 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
Philip Milne6216e872012-02-16 17:15:50 -08001789 * {@link GridLayout.Spec Specs} are immutable structures
Philip Milneb0ce49b2011-07-15 15:39:07 -07001790 * and may be shared between the layout parameters of different children.
Philip Milne3f8956d2011-05-13 17:29:00 +01001791 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001792 * The row and column specs contain the leading and trailing indices along each axis
Philip Milneaa616f32011-05-27 18:38:01 -07001793 * and together specify the four grid indices that delimit the cells of this cell group.
Philip Milne3f8956d2011-05-13 17:29:00 +01001794 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001795 * The alignment properties of the row and column specs together specify
Philip Milne3f8956d2011-05-13 17:29:00 +01001796 * both aspects of alignment within the cell group. It is also possible to specify a child's
1797 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
1798 * method.
Philip Milnef6679c82011-09-18 11:36:57 -07001799 *
1800 * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
1801 *
1802 * Because the default values of the {@link #width} and {@link #height}
1803 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
1804 * declared in the layout parameters of GridLayout's children. In addition,
1805 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
1806 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
1807 * instead controlled by the principle of <em>flexibility</em>,
1808 * as discussed in {@link GridLayout}.
1809 *
1810 * <h4>Summary</h4>
1811 *
1812 * You should not need to use either of the special size values:
1813 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
1814 * a GridLayout.
Philip Milne3f8956d2011-05-13 17:29:00 +01001815 *
1816 * <h4>Default values</h4>
1817 *
1818 * <ul>
1819 * <li>{@link #width} = {@link #WRAP_CONTENT}</li>
1820 * <li>{@link #height} = {@link #WRAP_CONTENT}</li>
1821 * <li>{@link #topMargin} = 0 when
1822 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001823 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001824 * indicate that a default value should be computed on demand. </li>
1825 * <li>{@link #leftMargin} = 0 when
1826 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001827 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001828 * indicate that a default value should be computed on demand. </li>
1829 * <li>{@link #bottomMargin} = 0 when
1830 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001831 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001832 * indicate that a default value should be computed on demand. </li>
1833 * <li>{@link #rightMargin} = 0 when
1834 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001835 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001836 * indicate that a default value should be computed on demand. </li>
Philip Milnef6679c82011-09-18 11:36:57 -07001837 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
1838 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
1839 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
1840 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
1841 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
Philip Milne6216e872012-02-16 17:15:50 -08001842 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01001843 * </ul>
1844 *
Philip Milnef6679c82011-09-18 11:36:57 -07001845 * See {@link GridLayout} for a more complete description of the conventions
1846 * used by GridLayout in the interpretation of the properties of this class.
1847 *
Philip Milne3f8956d2011-05-13 17:29:00 +01001848 * @attr ref android.R.styleable#GridLayout_Layout_layout_row
1849 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
Philip Milne3f8956d2011-05-13 17:29:00 +01001850 * @attr ref android.R.styleable#GridLayout_Layout_layout_column
1851 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
Philip Milne3f8956d2011-05-13 17:29:00 +01001852 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
1853 */
1854 public static class LayoutParams extends MarginLayoutParams {
1855
1856 // Default values
1857
1858 private static final int DEFAULT_WIDTH = WRAP_CONTENT;
1859 private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
1860 private static final int DEFAULT_MARGIN = UNDEFINED;
1861 private static final int DEFAULT_ROW = UNDEFINED;
1862 private static final int DEFAULT_COLUMN = UNDEFINED;
Philip Milnef4748702011-06-09 18:30:32 -07001863 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01001864 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
Philip Milne3f8956d2011-05-13 17:29:00 +01001865
1866 // TypedArray indices
1867
Philip Milneb0ce49b2011-07-15 15:39:07 -07001868 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin;
1869 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft;
1870 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop;
1871 private static final int RIGHT_MARGIN =
1872 R.styleable.ViewGroup_MarginLayout_layout_marginRight;
Philip Milne3f8956d2011-05-13 17:29:00 +01001873 private static final int BOTTOM_MARGIN =
Philip Milneb0ce49b2011-07-15 15:39:07 -07001874 R.styleable.ViewGroup_MarginLayout_layout_marginBottom;
Philip Milne3f8956d2011-05-13 17:29:00 +01001875
Philip Milneb0ce49b2011-07-15 15:39:07 -07001876 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
1877 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
Philip Milne5d1a9842011-07-07 11:47:08 -07001878
Philip Milneb0ce49b2011-07-15 15:39:07 -07001879 private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
1880 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
Philip Milne5d1a9842011-07-07 11:47:08 -07001881
Philip Milneb0ce49b2011-07-15 15:39:07 -07001882 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
Philip Milne3f8956d2011-05-13 17:29:00 +01001883
1884 // Instance variables
1885
1886 /**
Philip Milnef6679c82011-09-18 11:36:57 -07001887 * The spec that defines the vertical characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01001888 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08001889 * If an assignment is made to this field after a measurement or layout operation
1890 * has already taken place, a call to
1891 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
1892 * must be made to notify GridLayout of the change. GridLayout is normally able
1893 * to detect when code fails to observe this rule, issue a warning and take steps to
1894 * compensate for the omission. This facility is implemented on a best effort basis
1895 * and should not be relied upon in production code - so it is best to include the above
1896 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01001897 */
Philip Milnef6679c82011-09-18 11:36:57 -07001898 public Spec rowSpec = Spec.UNDEFINED;
1899
Philip Milne3f8956d2011-05-13 17:29:00 +01001900 /**
Philip Milnef6679c82011-09-18 11:36:57 -07001901 * The spec that defines the horizontal characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01001902 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08001903 * If an assignment is made to this field after a measurement or layout operation
1904 * has already taken place, a call to
1905 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
1906 * must be made to notify GridLayout of the change. GridLayout is normally able
1907 * to detect when code fails to observe this rule, issue a warning and take steps to
1908 * compensate for the omission. This facility is implemented on a best effort basis
1909 * and should not be relied upon in production code - so it is best to include the above
1910 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01001911 */
Philip Milnef6679c82011-09-18 11:36:57 -07001912 public Spec columnSpec = Spec.UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001913
1914 // Constructors
1915
1916 private LayoutParams(
1917 int width, int height,
1918 int left, int top, int right, int bottom,
Philip Milne93cd6a62011-07-12 14:49:45 -07001919 Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001920 super(width, height);
1921 setMargins(left, top, right, bottom);
Philip Milne93cd6a62011-07-12 14:49:45 -07001922 this.rowSpec = rowSpec;
1923 this.columnSpec = columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001924 }
1925
1926 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07001927 * Constructs a new LayoutParams instance for this <code>rowSpec</code>
1928 * and <code>columnSpec</code>. All other fields are initialized with
Philip Milne3f8956d2011-05-13 17:29:00 +01001929 * default values as defined in {@link LayoutParams}.
1930 *
Philip Milne93cd6a62011-07-12 14:49:45 -07001931 * @param rowSpec the rowSpec
1932 * @param columnSpec the columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +01001933 */
Philip Milne93cd6a62011-07-12 14:49:45 -07001934 public LayoutParams(Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001935 this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
1936 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
Philip Milne93cd6a62011-07-12 14:49:45 -07001937 rowSpec, columnSpec);
Philip Milne3f8956d2011-05-13 17:29:00 +01001938 }
1939
1940 /**
1941 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
1942 */
1943 public LayoutParams() {
Philip Milnef6679c82011-09-18 11:36:57 -07001944 this(Spec.UNDEFINED, Spec.UNDEFINED);
Philip Milne3f8956d2011-05-13 17:29:00 +01001945 }
1946
1947 // Copying constructors
1948
1949 /**
1950 * {@inheritDoc}
1951 */
1952 public LayoutParams(ViewGroup.LayoutParams params) {
1953 super(params);
1954 }
1955
1956 /**
1957 * {@inheritDoc}
1958 */
1959 public LayoutParams(MarginLayoutParams params) {
1960 super(params);
1961 }
1962
1963 /**
1964 * {@inheritDoc}
1965 */
1966 public LayoutParams(LayoutParams that) {
1967 super(that);
Philip Milnef6679c82011-09-18 11:36:57 -07001968 this.rowSpec = that.rowSpec;
1969 this.columnSpec = that.columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001970 }
1971
1972 // AttributeSet constructors
1973
Philip Milne3f8956d2011-05-13 17:29:00 +01001974 /**
1975 * {@inheritDoc}
1976 *
1977 * Values not defined in the attribute set take the default values
1978 * defined in {@link LayoutParams}.
1979 */
1980 public LayoutParams(Context context, AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -07001981 super(context, attrs);
1982 reInitSuper(context, attrs);
1983 init(context, attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001984 }
1985
1986 // Implementation
1987
Philip Milne3f8956d2011-05-13 17:29:00 +01001988 // Reinitialise the margins using a different default policy than MarginLayoutParams.
1989 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
1990 // so that a layout manager default can be accessed post set up. We need this as, at the
1991 // point of installation, we do not know how many rows/cols there are and therefore
1992 // which elements are positioned next to the container's trailing edges. We need to
1993 // know this as margins around the container's boundary should have different
1994 // defaults to those between peers.
1995
1996 // This method could be parametrized and moved into MarginLayout.
1997 private void reInitSuper(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07001998 TypedArray a =
1999 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
Philip Milne3f8956d2011-05-13 17:29:00 +01002000 try {
2001 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
2002
2003 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
2004 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
2005 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
2006 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
2007 } finally {
2008 a.recycle();
2009 }
2010 }
2011
Philip Milne5125e212011-07-21 11:39:37 -07002012 private void init(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07002013 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
Philip Milne3f8956d2011-05-13 17:29:00 +01002014 try {
Philip Milne5125e212011-07-21 11:39:37 -07002015 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
Philip Milne3f8956d2011-05-13 17:29:00 +01002016
Philip Milne1e548252011-06-16 19:02:33 -07002017 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
Philip Milne5125e212011-07-21 11:39:37 -07002018 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne899d5922011-07-21 11:39:37 -07002019 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01002020
Philip Milne1e548252011-06-16 19:02:33 -07002021 int row = a.getInt(ROW, DEFAULT_ROW);
2022 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne899d5922011-07-21 11:39:37 -07002023 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
Philip Milne3f8956d2011-05-13 17:29:00 +01002024 } finally {
2025 a.recycle();
2026 }
2027 }
2028
2029 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002030 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
Philip Milne6216e872012-02-16 17:15:50 -08002031 * See {@link Gravity}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002032 *
Philip Milne7fd94872011-06-07 20:14:17 -07002033 * @param gravity the new gravity value
Philip Milne3f8956d2011-05-13 17:29:00 +01002034 *
2035 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
2036 */
2037 public void setGravity(int gravity) {
Philip Milne5125e212011-07-21 11:39:37 -07002038 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
2039 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01002040 }
2041
2042 @Override
2043 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
2044 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
2045 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
2046 }
2047
Philip Milnef6679c82011-09-18 11:36:57 -07002048 final void setRowSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002049 rowSpec = rowSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002050 }
2051
Philip Milnef6679c82011-09-18 11:36:57 -07002052 final void setColumnSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002053 columnSpec = columnSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002054 }
Philip Milned7dd8902012-01-26 16:55:30 -08002055
2056 @Override
2057 public boolean equals(Object o) {
2058 if (this == o) return true;
2059 if (o == null || getClass() != o.getClass()) return false;
2060
2061 LayoutParams that = (LayoutParams) o;
2062
2063 if (!columnSpec.equals(that.columnSpec)) return false;
2064 if (!rowSpec.equals(that.rowSpec)) return false;
2065
2066 return true;
2067 }
2068
2069 @Override
2070 public int hashCode() {
2071 int result = rowSpec.hashCode();
2072 result = 31 * result + columnSpec.hashCode();
2073 return result;
2074 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002075 }
2076
Philip Milneaa616f32011-05-27 18:38:01 -07002077 /*
2078 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
2079 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
2080 */
Philip Milnef6679c82011-09-18 11:36:57 -07002081 final static class Arc {
Philip Milne3f8956d2011-05-13 17:29:00 +01002082 public final Interval span;
Philip Milneaa616f32011-05-27 18:38:01 -07002083 public final MutableInt value;
Philip Milne48b55242011-06-29 11:09:45 -07002084 public boolean valid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01002085
Philip Milneaa616f32011-05-27 18:38:01 -07002086 public Arc(Interval span, MutableInt value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002087 this.span = span;
2088 this.value = value;
2089 }
2090
2091 @Override
2092 public String toString() {
Philip Milne48b55242011-06-29 11:09:45 -07002093 return span + " " + (!valid ? "+>" : "->") + " " + value;
Philip Milne3f8956d2011-05-13 17:29:00 +01002094 }
2095 }
2096
2097 // A mutable Integer - used to avoid heap allocation during the layout operation
2098
Philip Milnef6679c82011-09-18 11:36:57 -07002099 final static class MutableInt {
Philip Milne3f8956d2011-05-13 17:29:00 +01002100 public int value;
2101
Philip Milnef6679c82011-09-18 11:36:57 -07002102 public MutableInt() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002103 reset();
2104 }
2105
Philip Milnef6679c82011-09-18 11:36:57 -07002106 public MutableInt(int value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002107 this.value = value;
2108 }
2109
Philip Milnef6679c82011-09-18 11:36:57 -07002110 public void reset() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002111 value = Integer.MIN_VALUE;
2112 }
Philip Milne48b55242011-06-29 11:09:45 -07002113
2114 @Override
2115 public String toString() {
2116 return Integer.toString(value);
2117 }
2118 }
2119
Philip Milnef6679c82011-09-18 11:36:57 -07002120 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
Philip Milne48b55242011-06-29 11:09:45 -07002121 private final Class<K> keyType;
2122 private final Class<V> valueType;
2123
2124 private Assoc(Class<K> keyType, Class<V> valueType) {
2125 this.keyType = keyType;
2126 this.valueType = valueType;
2127 }
2128
Philip Milnef6679c82011-09-18 11:36:57 -07002129 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
Philip Milne48b55242011-06-29 11:09:45 -07002130 return new Assoc<K, V>(keyType, valueType);
2131 }
2132
2133 public void put(K key, V value) {
2134 add(Pair.create(key, value));
2135 }
2136
2137 @SuppressWarnings(value = "unchecked")
2138 public PackedMap<K, V> pack() {
2139 int N = size();
2140 K[] keys = (K[]) Array.newInstance(keyType, N);
2141 V[] values = (V[]) Array.newInstance(valueType, N);
2142 for (int i = 0; i < N; i++) {
2143 keys[i] = get(i).first;
2144 values[i] = get(i).second;
2145 }
2146 return new PackedMap<K, V>(keys, values);
2147 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002148 }
2149
Philip Milneaa616f32011-05-27 18:38:01 -07002150 /*
2151 This data structure is used in place of a Map where we have an index that refers to the order
2152 in which each key/value pairs were added to the map. In this case we store keys and values
2153 in arrays of a length that is equal to the number of unique keys. We also maintain an
2154 array of indexes from insertion order to the compacted arrays of keys and values.
2155
2156 Note that behavior differs from that of a LinkedHashMap in that repeated entries
2157 *do* get added multiples times. So the length of index is equals to the number of
2158 items added.
2159
2160 This is useful in the GridLayout class where we can rely on the order of children not
2161 changing during layout - to use integer-based lookup for our internal structures
2162 rather than using (and storing) an implementation of Map<Key, ?>.
2163 */
Philip Milne3f8956d2011-05-13 17:29:00 +01002164 @SuppressWarnings(value = "unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -07002165 final static class PackedMap<K, V> {
Philip Milne3f8956d2011-05-13 17:29:00 +01002166 public final int[] index;
2167 public final K[] keys;
2168 public final V[] values;
2169
2170 private PackedMap(K[] keys, V[] values) {
2171 this.index = createIndex(keys);
2172
Philip Milneaa616f32011-05-27 18:38:01 -07002173 this.keys = compact(keys, index);
2174 this.values = compact(values, index);
Philip Milne3f8956d2011-05-13 17:29:00 +01002175 }
2176
Philip Milnef6679c82011-09-18 11:36:57 -07002177 public V getValue(int i) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002178 return values[index[i]];
2179 }
2180
2181 private static <K> int[] createIndex(K[] keys) {
2182 int size = keys.length;
2183 int[] result = new int[size];
2184
2185 Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
2186 for (int i = 0; i < size; i++) {
2187 K key = keys[i];
2188 Integer index = keyToIndex.get(key);
2189 if (index == null) {
2190 index = keyToIndex.size();
2191 keyToIndex.put(key, index);
2192 }
2193 result[i] = index;
2194 }
2195 return result;
2196 }
2197
Philip Milneaa616f32011-05-27 18:38:01 -07002198 /*
2199 Create a compact array of keys or values using the supplied index.
2200 */
2201 private static <K> K[] compact(K[] a, int[] index) {
2202 int size = a.length;
2203 Class<?> componentType = a.getClass().getComponentType();
Philip Milne51f17d52011-06-13 10:44:49 -07002204 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01002205
2206 // this overwrite duplicates, retaining the last equivalent entry
2207 for (int i = 0; i < size; i++) {
Philip Milneaa616f32011-05-27 18:38:01 -07002208 result[index[i]] = a[i];
Philip Milne3f8956d2011-05-13 17:29:00 +01002209 }
2210 return result;
2211 }
2212 }
2213
Philip Milneaa616f32011-05-27 18:38:01 -07002214 /*
Philip Milne93cd6a62011-07-12 14:49:45 -07002215 For each group (with a given alignment) we need to store the amount of space required
Philip Milne7fd94872011-06-07 20:14:17 -07002216 before the alignment point and the amount of space required after it. One side of this
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002217 calculation is always 0 for START and END alignments but we don't make use of this.
Philip Milneaa616f32011-05-27 18:38:01 -07002218 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
2219 simple optimisations are possible.
2220
2221 The general algorithm therefore is to create a Map (actually a PackedMap) from
Philip Milne93cd6a62011-07-12 14:49:45 -07002222 group to Bounds and to loop through all Views in the group taking the maximum
Philip Milneaa616f32011-05-27 18:38:01 -07002223 of the values for each View.
2224 */
Philip Milnef6679c82011-09-18 11:36:57 -07002225 static class Bounds {
Philip Milne7fd94872011-06-07 20:14:17 -07002226 public int before;
2227 public int after;
Philip Milne5125e212011-07-21 11:39:37 -07002228 public int flexibility; // we're flexible iff all included specs are flexible
Philip Milne3f8956d2011-05-13 17:29:00 +01002229
2230 private Bounds() {
2231 reset();
2232 }
2233
Philip Milnea1f7b102011-06-23 11:10:13 -07002234 protected void reset() {
Philip Milne7fd94872011-06-07 20:14:17 -07002235 before = Integer.MIN_VALUE;
2236 after = Integer.MIN_VALUE;
Philip Milne5125e212011-07-21 11:39:37 -07002237 flexibility = CAN_STRETCH; // from the above, we're flexible when empty
Philip Milne3f8956d2011-05-13 17:29:00 +01002238 }
2239
Philip Milnea1f7b102011-06-23 11:10:13 -07002240 protected void include(int before, int after) {
Philip Milne7fd94872011-06-07 20:14:17 -07002241 this.before = max(this.before, before);
2242 this.after = max(this.after, after);
Philip Milne3f8956d2011-05-13 17:29:00 +01002243 }
2244
Philip Milne48b55242011-06-29 11:09:45 -07002245 protected int size(boolean min) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002246 if (!min) {
Philip Milne5125e212011-07-21 11:39:37 -07002247 if (canStretch(flexibility)) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002248 return MAX_SIZE;
2249 }
Philip Milne48b55242011-06-29 11:09:45 -07002250 }
Philip Milne7fd94872011-06-07 20:14:17 -07002251 return before + after;
Philip Milne3f8956d2011-05-13 17:29:00 +01002252 }
2253
Philip Milne1557fd72012-04-04 23:41:34 -07002254 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
Philip Milne7a23b492012-04-24 22:12:36 -07002255 return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne1557fd72012-04-04 23:41:34 -07002256 }
2257
2258 protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
Philip Milne5125e212011-07-21 11:39:37 -07002259 this.flexibility &= spec.getFlexibility();
Philip Milne1557fd72012-04-04 23:41:34 -07002260 boolean horizontal = axis.horizontal;
2261 int size = gl.getMeasurementIncludingMargin(c, horizontal);
2262 Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002263 // todo test this works correctly when the returned value is UNDEFINED
Philip Milne7a23b492012-04-24 22:12:36 -07002264 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne48b55242011-06-29 11:09:45 -07002265 include(before, size - before);
Philip Milnea1f7b102011-06-23 11:10:13 -07002266 }
2267
Philip Milne3f8956d2011-05-13 17:29:00 +01002268 @Override
2269 public String toString() {
2270 return "Bounds{" +
Philip Milne7fd94872011-06-07 20:14:17 -07002271 "before=" + before +
2272 ", after=" + after +
Philip Milne3f8956d2011-05-13 17:29:00 +01002273 '}';
2274 }
2275 }
2276
2277 /**
2278 * An Interval represents a contiguous range of values that lie between
2279 * the interval's {@link #min} and {@link #max} values.
2280 * <p>
2281 * Intervals are immutable so may be passed as values and used as keys in hash tables.
2282 * It is not necessary to have multiple instances of Intervals which have the same
2283 * {@link #min} and {@link #max} values.
2284 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -07002285 * Intervals are often written as {@code [min, max]} and represent the set of values
2286 * {@code x} such that {@code min <= x < max}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002287 */
Philip Milnef6679c82011-09-18 11:36:57 -07002288 final static class Interval {
Philip Milne3f8956d2011-05-13 17:29:00 +01002289 /**
2290 * The minimum value.
2291 */
2292 public final int min;
Philip Milneaa616f32011-05-27 18:38:01 -07002293
Philip Milne3f8956d2011-05-13 17:29:00 +01002294 /**
2295 * The maximum value.
2296 */
2297 public final int max;
2298
2299 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002300 * Construct a new Interval, {@code interval}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002301 * <ul>
Philip Milne7fd94872011-06-07 20:14:17 -07002302 * <li> {@code interval.min = min} </li>
2303 * <li> {@code interval.max = max} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002304 * </ul>
2305 *
2306 * @param min the minimum value.
2307 * @param max the maximum value.
2308 */
2309 public Interval(int min, int max) {
2310 this.min = min;
2311 this.max = max;
2312 }
2313
Philip Milnef6679c82011-09-18 11:36:57 -07002314 int size() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002315 return max - min;
2316 }
2317
Philip Milnef6679c82011-09-18 11:36:57 -07002318 Interval inverse() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002319 return new Interval(max, min);
2320 }
2321
2322 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002323 * Returns {@code true} if the {@link #getClass class},
2324 * {@link #min} and {@link #max} properties of this Interval and the
2325 * supplied parameter are pairwise equal; {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002326 *
Philip Milne7fd94872011-06-07 20:14:17 -07002327 * @param that the object to compare this interval with
Philip Milne3f8956d2011-05-13 17:29:00 +01002328 *
2329 * @return {@code true} if the specified object is equal to this
Philip Milne7fd94872011-06-07 20:14:17 -07002330 * {@code Interval}, {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002331 */
2332 @Override
2333 public boolean equals(Object that) {
2334 if (this == that) {
2335 return true;
2336 }
2337 if (that == null || getClass() != that.getClass()) {
2338 return false;
2339 }
2340
2341 Interval interval = (Interval) that;
2342
2343 if (max != interval.max) {
2344 return false;
2345 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002346 //noinspection RedundantIfStatement
Philip Milne3f8956d2011-05-13 17:29:00 +01002347 if (min != interval.min) {
2348 return false;
2349 }
2350
2351 return true;
2352 }
2353
2354 @Override
2355 public int hashCode() {
2356 int result = min;
2357 result = 31 * result + max;
2358 return result;
2359 }
2360
2361 @Override
2362 public String toString() {
2363 return "[" + min + ", " + max + "]";
2364 }
2365 }
2366
Philip Milne899d5922011-07-21 11:39:37 -07002367 /**
2368 * A Spec defines the horizontal or vertical characteristics of a group of
Philip Milne4a145d72011-09-29 14:14:25 -07002369 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em>
2370 * along the appropriate axis.
Philip Milne899d5922011-07-21 11:39:37 -07002371 * <p>
2372 * The <em>grid indices</em> are the leading and trailing edges of this cell group.
2373 * See {@link GridLayout} for a description of the conventions used by GridLayout
2374 * for grid indices.
2375 * <p>
2376 * The <em>alignment</em> property specifies how cells should be aligned in this group.
2377 * For row groups, this specifies the vertical alignment.
2378 * For column groups, this specifies the horizontal alignment.
Philip Milne4a145d72011-09-29 14:14:25 -07002379 * <p>
2380 * Use the following static methods to create specs:
2381 * <ul>
2382 * <li>{@link #spec(int)}</li>
2383 * <li>{@link #spec(int, int)}</li>
2384 * <li>{@link #spec(int, Alignment)}</li>
2385 * <li>{@link #spec(int, int, Alignment)}</li>
2386 * </ul>
2387 *
Philip Milne899d5922011-07-21 11:39:37 -07002388 */
Philip Milne93cd6a62011-07-12 14:49:45 -07002389 public static class Spec {
Philip Milnef6679c82011-09-18 11:36:57 -07002390 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
2391
2392 final boolean startDefined;
Philip Milne48b55242011-06-29 11:09:45 -07002393 final Interval span;
Philip Milne93cd6a62011-07-12 14:49:45 -07002394 final Alignment alignment;
Philip Milne3f8956d2011-05-13 17:29:00 +01002395
Philip Milnef6679c82011-09-18 11:36:57 -07002396 private Spec(boolean startDefined, Interval span, Alignment alignment) {
2397 this.startDefined = startDefined;
Philip Milne5d1a9842011-07-07 11:47:08 -07002398 this.span = span;
2399 this.alignment = alignment;
Philip Milne5d1a9842011-07-07 11:47:08 -07002400 }
2401
Philip Milnef6679c82011-09-18 11:36:57 -07002402 private Spec(boolean startDefined, int start, int size, Alignment alignment) {
2403 this(startDefined, new Interval(start, start + size), alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002404 }
2405
Philip Milnef6679c82011-09-18 11:36:57 -07002406 final Spec copyWriteSpan(Interval span) {
2407 return new Spec(startDefined, span, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002408 }
2409
Philip Milnef6679c82011-09-18 11:36:57 -07002410 final Spec copyWriteAlignment(Alignment alignment) {
2411 return new Spec(startDefined, span, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002412 }
2413
Philip Milnef6679c82011-09-18 11:36:57 -07002414 final int getFlexibility() {
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002415 return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
Philip Milne5d1a9842011-07-07 11:47:08 -07002416 }
2417
Philip Milne3f8956d2011-05-13 17:29:00 +01002418 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002419 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
2420 * properties of this Spec and the supplied parameter are pairwise equal,
Philip Milne7fd94872011-06-07 20:14:17 -07002421 * {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002422 *
Philip Milne93cd6a62011-07-12 14:49:45 -07002423 * @param that the object to compare this spec with
Philip Milne3f8956d2011-05-13 17:29:00 +01002424 *
2425 * @return {@code true} if the specified object is equal to this
Philip Milne93cd6a62011-07-12 14:49:45 -07002426 * {@code Spec}; {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +01002427 */
2428 @Override
2429 public boolean equals(Object that) {
2430 if (this == that) {
2431 return true;
2432 }
2433 if (that == null || getClass() != that.getClass()) {
2434 return false;
2435 }
2436
Philip Milne93cd6a62011-07-12 14:49:45 -07002437 Spec spec = (Spec) that;
Philip Milne3f8956d2011-05-13 17:29:00 +01002438
Philip Milne93cd6a62011-07-12 14:49:45 -07002439 if (!alignment.equals(spec.alignment)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002440 return false;
2441 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002442 //noinspection RedundantIfStatement
Philip Milne93cd6a62011-07-12 14:49:45 -07002443 if (!span.equals(spec.span)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002444 return false;
2445 }
2446
2447 return true;
2448 }
2449
2450 @Override
2451 public int hashCode() {
2452 int result = span.hashCode();
2453 result = 31 * result + alignment.hashCode();
2454 return result;
2455 }
2456 }
2457
Philip Milne3f8956d2011-05-13 17:29:00 +01002458 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002459 * Return a Spec, {@code spec}, where:
2460 * <ul>
2461 * <li> {@code spec.span = [start, start + size]} </li>
2462 * <li> {@code spec.alignment = alignment} </li>
2463 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002464 * <p>
2465 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002466 *
2467 * @param start the start
2468 * @param size the size
2469 * @param alignment the alignment
2470 */
2471 public static Spec spec(int start, int size, Alignment alignment) {
Philip Milnef6679c82011-09-18 11:36:57 -07002472 return new Spec(start != UNDEFINED, start, size, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002473 }
2474
2475 /**
2476 * Return a Spec, {@code spec}, where:
2477 * <ul>
2478 * <li> {@code spec.span = [start, start + 1]} </li>
2479 * <li> {@code spec.alignment = alignment} </li>
2480 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002481 * <p>
2482 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002483 *
2484 * @param start the start index
2485 * @param alignment the alignment
Philip Milne7b757812012-09-19 18:13:44 -07002486 *
2487 * @see #spec(int, int, Alignment)
Philip Milne93cd6a62011-07-12 14:49:45 -07002488 */
2489 public static Spec spec(int start, Alignment alignment) {
2490 return spec(start, 1, alignment);
2491 }
2492
2493 /**
Philip Milne5125e212011-07-21 11:39:37 -07002494 * Return a Spec, {@code spec}, where:
2495 * <ul>
2496 * <li> {@code spec.span = [start, start + size]} </li>
2497 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002498 * <p>
2499 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002500 *
2501 * @param start the start
2502 * @param size the size
Philip Milne7b757812012-09-19 18:13:44 -07002503 *
2504 * @see #spec(int, Alignment)
Philip Milne5125e212011-07-21 11:39:37 -07002505 */
2506 public static Spec spec(int start, int size) {
2507 return spec(start, size, UNDEFINED_ALIGNMENT);
2508 }
2509
2510 /**
2511 * Return a Spec, {@code spec}, where:
2512 * <ul>
2513 * <li> {@code spec.span = [start, start + 1]} </li>
2514 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002515 * <p>
2516 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002517 *
2518 * @param start the start index
Philip Milne7b757812012-09-19 18:13:44 -07002519 *
2520 * @see #spec(int, int)
Philip Milne5125e212011-07-21 11:39:37 -07002521 */
2522 public static Spec spec(int start) {
2523 return spec(start, 1);
2524 }
2525
2526 /**
Philip Milne3f8956d2011-05-13 17:29:00 +01002527 * Alignments specify where a view should be placed within a cell group and
2528 * what size it should be.
2529 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07002530 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
2531 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
2532 * {@code alignment}. Overall placement of the view in the cell
Philip Milne3f8956d2011-05-13 17:29:00 +01002533 * group is specified by the two alignments which act along each axis independently.
2534 * <p>
Philip Milnea1f7b102011-06-23 11:10:13 -07002535 * The GridLayout class defines the most common alignments used in general layout:
Philip Milne6216e872012-02-16 17:15:50 -08002536 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
2537 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
Philip Milnea1f7b102011-06-23 11:10:13 -07002538 */
2539 /*
Philip Milnec9885f62011-06-15 17:07:35 -07002540 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
Philip Milne3f8956d2011-05-13 17:29:00 +01002541 * to return the appropriate value for the type of alignment being defined.
2542 * The enclosing algorithms position the children
Philip Milne1e548252011-06-16 19:02:33 -07002543 * so that the locations defined by the alignment values
Philip Milne3f8956d2011-05-13 17:29:00 +01002544 * are the same for all of the views in a group.
2545 * <p>
Philip Milne3f8956d2011-05-13 17:29:00 +01002546 */
Philip Milnec9885f62011-06-15 17:07:35 -07002547 public static abstract class Alignment {
Philip Milne48b55242011-06-29 11:09:45 -07002548 Alignment() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002549 }
2550
Philip Milne6216e872012-02-16 17:15:50 -08002551 abstract int getGravityOffset(View view, int cellDelta);
2552
Philip Milne3f8956d2011-05-13 17:29:00 +01002553 /**
2554 * Returns an alignment value. In the case of vertical alignments the value
2555 * returned should indicate the distance from the top of the view to the
2556 * alignment location.
2557 * For horizontal alignments measurement is made from the left edge of the component.
2558 *
Philip Milnec9885f62011-06-15 17:07:35 -07002559 * @param view the view to which this alignment should be applied
2560 * @param viewSize the measured size of the view
Philip Milne7a23b492012-04-24 22:12:36 -07002561 * @param mode the basis of alignment: CLIP or OPTICAL
Philip Milneb3a8c542011-06-20 16:02:59 -07002562 * @return the alignment value
Philip Milne3f8956d2011-05-13 17:29:00 +01002563 */
Philip Milne7a23b492012-04-24 22:12:36 -07002564 abstract int getAlignmentValue(View view, int viewSize, int mode);
Philip Milne3f8956d2011-05-13 17:29:00 +01002565
2566 /**
2567 * Returns the size of the view specified by this alignment.
2568 * In the case of vertical alignments this method should return a height; for
2569 * horizontal alignments this method should return the width.
Philip Milnec9885f62011-06-15 17:07:35 -07002570 * <p>
2571 * The default implementation returns {@code viewSize}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002572 *
Philip Milnec9885f62011-06-15 17:07:35 -07002573 * @param view the view to which this alignment should be applied
2574 * @param viewSize the measured size of the view
2575 * @param cellSize the size of the cell into which this view will be placed
Philip Milneb3a8c542011-06-20 16:02:59 -07002576 * @return the aligned size
Philip Milne3f8956d2011-05-13 17:29:00 +01002577 */
Philip Milne6216e872012-02-16 17:15:50 -08002578 int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002579 return viewSize;
2580 }
Philip Milnea1f7b102011-06-23 11:10:13 -07002581
Philip Milne48b55242011-06-29 11:09:45 -07002582 Bounds getBounds() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002583 return new Bounds();
2584 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002585 }
2586
Philip Milnef6679c82011-09-18 11:36:57 -07002587 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002588 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002589 int getGravityOffset(View view, int cellDelta) {
2590 return UNDEFINED;
2591 }
2592
2593 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002594 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne5125e212011-07-21 11:39:37 -07002595 return UNDEFINED;
2596 }
2597 };
2598
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002599 /**
2600 * Indicates that a view should be aligned with the <em>start</em>
2601 * edges of the other views in its cell group.
2602 */
Philip Milnec9885f62011-06-15 17:07:35 -07002603 private static final Alignment LEADING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002604 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002605 int getGravityOffset(View view, int cellDelta) {
2606 return 0;
2607 }
2608
2609 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002610 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002611 return 0;
2612 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002613 };
2614
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002615 /**
2616 * Indicates that a view should be aligned with the <em>end</em>
2617 * edges of the other views in its cell group.
2618 */
Philip Milnec9885f62011-06-15 17:07:35 -07002619 private static final Alignment TRAILING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002620 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002621 int getGravityOffset(View view, int cellDelta) {
2622 return cellDelta;
2623 }
2624
2625 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002626 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002627 return viewSize;
2628 }
2629 };
2630
2631 /**
2632 * Indicates that a view should be aligned with the <em>top</em>
2633 * edges of the other views in its cell group.
2634 */
2635 public static final Alignment TOP = LEADING;
2636
2637 /**
2638 * Indicates that a view should be aligned with the <em>bottom</em>
2639 * edges of the other views in its cell group.
2640 */
2641 public static final Alignment BOTTOM = TRAILING;
2642
2643 /**
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002644 * Indicates that a view should be aligned with the <em>start</em>
Philip Milne3f8956d2011-05-13 17:29:00 +01002645 * edges of the other views in its cell group.
2646 */
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002647 public static final Alignment START = LEADING;
2648
2649 /**
2650 * Indicates that a view should be aligned with the <em>end</em>
2651 * edges of the other views in its cell group.
2652 */
2653 public static final Alignment END = TRAILING;
2654
Philip Milne6216e872012-02-16 17:15:50 -08002655 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002656 return new Alignment() {
2657 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002658 int getGravityOffset(View view, int cellDelta) {
2659 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta);
2660 }
2661
2662 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002663 public int getAlignmentValue(View view, int viewSize, int mode) {
2664 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002665 }
2666 };
2667 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002668
2669 /**
2670 * Indicates that a view should be aligned with the <em>left</em>
2671 * edges of the other views in its cell group.
2672 */
Philip Milne6216e872012-02-16 17:15:50 -08002673 public static final Alignment LEFT = createSwitchingAlignment(START, END);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002674
2675 /**
2676 * Indicates that a view should be aligned with the <em>right</em>
2677 * edges of the other views in its cell group.
2678 */
Philip Milne6216e872012-02-16 17:15:50 -08002679 public static final Alignment RIGHT = createSwitchingAlignment(END, START);
Philip Milne3f8956d2011-05-13 17:29:00 +01002680
2681 /**
2682 * Indicates that a view should be <em>centered</em> with the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002683 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
2684 * LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002685 */
Philip Milnec9885f62011-06-15 17:07:35 -07002686 public static final Alignment CENTER = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002687 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002688 int getGravityOffset(View view, int cellDelta) {
2689 return cellDelta >> 1;
2690 }
2691
2692 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002693 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002694 return viewSize >> 1;
2695 }
2696 };
2697
2698 /**
2699 * Indicates that a view should be aligned with the <em>baselines</em>
2700 * of the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002701 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002702 *
2703 * @see View#getBaseline()
2704 */
Philip Milnec9885f62011-06-15 17:07:35 -07002705 public static final Alignment BASELINE = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002706 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002707 int getGravityOffset(View view, int cellDelta) {
2708 return 0; // baseline gravity is top
2709 }
2710
2711 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002712 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milnea8416442013-02-25 10:49:39 -08002713 if (view.getVisibility() == GONE) {
2714 return 0;
2715 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002716 int baseline = view.getBaseline();
Philip Milne7b757812012-09-19 18:13:44 -07002717 return baseline == -1 ? UNDEFINED : baseline;
Philip Milnea1f7b102011-06-23 11:10:13 -07002718 }
2719
2720 @Override
2721 public Bounds getBounds() {
2722 return new Bounds() {
2723 /*
2724 In a baseline aligned row in which some components define a baseline
2725 and some don't, we need a third variable to properly account for all
2726 the sizes. This tracks the maximum size of all the components -
2727 including those that don't define a baseline.
2728 */
2729 private int size;
2730
2731 @Override
2732 protected void reset() {
2733 super.reset();
Philip Milne48b55242011-06-29 11:09:45 -07002734 size = Integer.MIN_VALUE;
Philip Milnea1f7b102011-06-23 11:10:13 -07002735 }
2736
2737 @Override
2738 protected void include(int before, int after) {
2739 super.include(before, after);
2740 size = max(size, before + after);
2741 }
2742
2743 @Override
Philip Milne48b55242011-06-29 11:09:45 -07002744 protected int size(boolean min) {
2745 return max(super.size(min), size);
Philip Milnea1f7b102011-06-23 11:10:13 -07002746 }
2747
2748 @Override
Philip Milne1557fd72012-04-04 23:41:34 -07002749 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
2750 return max(0, super.getOffset(gl, c, a, size, hrz));
Philip Milnea1f7b102011-06-23 11:10:13 -07002751 }
2752 };
Philip Milne3f8956d2011-05-13 17:29:00 +01002753 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002754 };
2755
2756 /**
2757 * Indicates that a view should expanded to fit the boundaries of its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002758 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
2759 * {@link LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002760 */
2761 public static final Alignment FILL = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002762 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002763 int getGravityOffset(View view, int cellDelta) {
2764 return 0;
2765 }
2766
2767 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002768 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002769 return UNDEFINED;
2770 }
2771
Philip Milnec9885f62011-06-15 17:07:35 -07002772 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002773 public int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002774 return cellSize;
2775 }
2776 };
Philip Milne48b55242011-06-29 11:09:45 -07002777
Philip Milnef6679c82011-09-18 11:36:57 -07002778 static boolean canStretch(int flexibility) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002779 return (flexibility & CAN_STRETCH) != 0;
2780 }
2781
Philip Milne5125e212011-07-21 11:39:37 -07002782 private static final int INFLEXIBLE = 0;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002783 private static final int CAN_STRETCH = 2;
Jim Miller452eec32011-06-16 18:32:44 -07002784}