blob: 230900176a683f0b537274a04f9c3b64dc9c2557 [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 Milne48b55242011-06-29 11:09:45 -070027import android.util.Pair;
Philip Milne3f8956d2011-05-13 17:29:00 +010028import android.view.Gravity;
29import android.view.View;
30import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080031import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityNodeInfo;
Philip Milneedd69512012-03-14 17:21:33 -070033import android.widget.RemoteViews.RemoteView;
Philip Milne10ca24a2012-04-23 15:38:27 -070034import com.android.internal.R;
Philip Milne3f8956d2011-05-13 17:29:00 +010035
36import java.lang.reflect.Array;
37import java.util.ArrayList;
38import java.util.Arrays;
Philip Milne3f8956d2011-05-13 17:29:00 +010039import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42
Philip Milne5125e212011-07-21 11:39:37 -070043import static android.view.Gravity.*;
Philip Milne899d5922011-07-21 11:39:37 -070044import static android.view.View.MeasureSpec.EXACTLY;
45import static android.view.View.MeasureSpec.makeMeasureSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +010046import static java.lang.Math.max;
47import static java.lang.Math.min;
48
49/**
50 * A layout that places its children in a rectangular <em>grid</em>.
51 * <p>
52 * The grid is composed of a set of infinitely thin lines that separate the
53 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
Philip Milne7fd94872011-06-07 20:14:17 -070054 * by grid <em>indices</em>. A grid with {@code N} columns
55 * has {@code N + 1} grid indices that run from {@code 0}
56 * through {@code N} inclusive. Regardless of how GridLayout is
57 * configured, grid index {@code 0} is fixed to the leading edge of the
58 * container and grid index {@code N} is fixed to its trailing edge
Philip Milne3f8956d2011-05-13 17:29:00 +010059 * (after padding is taken into account).
60 *
Philip Milne93cd6a62011-07-12 14:49:45 -070061 * <h4>Row and Column Specs</h4>
Philip Milne3f8956d2011-05-13 17:29:00 +010062 *
63 * Children occupy one or more contiguous cells, as defined
Philip Milne93cd6a62011-07-12 14:49:45 -070064 * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and
65 * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters.
66 * Each spec defines the set of rows or columns that are to be
Philip Milne3f8956d2011-05-13 17:29:00 +010067 * occupied; and how children should be aligned within the resulting group of cells.
68 * Although cells do not normally overlap in a GridLayout, GridLayout does
69 * not prevent children being defined to occupy the same cell or group of cells.
70 * In this case however, there is no guarantee that children will not themselves
71 * overlap after the layout operation completes.
72 *
73 * <h4>Default Cell Assignment</h4>
74 *
Philip Milne48b55242011-06-29 11:09:45 -070075 * If a child does not specify the row and column indices of the cell it
Philip Milne3f8956d2011-05-13 17:29:00 +010076 * wishes to occupy, GridLayout assigns cell locations automatically using its:
77 * {@link GridLayout#setOrientation(int) orientation},
78 * {@link GridLayout#setRowCount(int) rowCount} and
79 * {@link GridLayout#setColumnCount(int) columnCount} properties.
80 *
81 * <h4>Space</h4>
82 *
83 * Space between children may be specified either by using instances of the
84 * dedicated {@link Space} view or by setting the
85 *
86 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
87 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
88 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
89 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
90 *
91 * layout parameters. When the
92 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
93 * property is set, default margins around children are automatically
Philip Milnef6679c82011-09-18 11:36:57 -070094 * allocated based on the prevailing UI style guide for the platform.
95 * Each of the margins so defined may be independently overridden by an assignment
Philip Milne3f8956d2011-05-13 17:29:00 +010096 * to the appropriate layout parameter.
Philip Milnef6679c82011-09-18 11:36:57 -070097 * Default values will generally produce a reasonable spacing between components
98 * but values may change between different releases of the platform.
Philip Milne3f8956d2011-05-13 17:29:00 +010099 *
100 * <h4>Excess Space Distribution</h4>
101 *
Philip Milnef6679c82011-09-18 11:36:57 -0700102 * GridLayout's distribution of excess space is based on <em>priority</em>
103 * rather than <em>weight</em>.
104 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700105 * A child's ability to stretch is inferred from the alignment properties of
106 * its row and column groups (which are typically set by setting the
107 * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
108 * If alignment was defined along a given axis then the component
Philip Milnef6679c82011-09-18 11:36:57 -0700109 * is taken as <em>flexible</em> in that direction. If no alignment was set,
110 * the component is instead assumed to be <em>inflexible</em>.
111 * <p>
112 * Multiple components in the same row or column group are
113 * considered to act in <em>parallel</em>. Such a
Philip Milne899d5922011-07-21 11:39:37 -0700114 * group is flexible only if <em>all</em> of the components
115 * within it are flexible. Row and column groups that sit either side of a common boundary
116 * are instead considered to act in <em>series</em>. The composite group made of these two
117 * elements is flexible if <em>one</em> of its elements is flexible.
118 * <p>
119 * To make a column stretch, make sure all of the components inside it define a
120 * gravity. To prevent a column from stretching, ensure that one of the components
121 * in the column does not define a gravity.
Philip Milne3f8956d2011-05-13 17:29:00 +0100122 * <p>
Philip Milnef6679c82011-09-18 11:36:57 -0700123 * When the principle of flexibility does not provide complete disambiguation,
124 * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
125 * and <em>bottom</em> edges.
126 *
127 * <h5>Limitations</h5>
128 *
129 * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
130 * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
Philip Milne6216e872012-02-16 17:15:50 -0800131 * to configure a GridLayout to distribute excess space between multiple components.
Philip Milnef6679c82011-09-18 11:36:57 -0700132 * <p>
133 * Some common use-cases may nevertheless be accommodated as follows.
134 * To place equal amounts of space around a component in a cell group;
135 * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
136 * For complete control over excess space distribution in a row or column;
137 * use a {@link LinearLayout} subview to hold the components in the associated cell group.
138 * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
Philip Milne3f8956d2011-05-13 17:29:00 +0100139 * <p>
140 * See {@link GridLayout.LayoutParams} for a full description of the
141 * layout parameters used by GridLayout.
142 *
143 * @attr ref android.R.styleable#GridLayout_orientation
144 * @attr ref android.R.styleable#GridLayout_rowCount
145 * @attr ref android.R.styleable#GridLayout_columnCount
146 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
147 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
148 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
149 */
Philip Milneedd69512012-03-14 17:21:33 -0700150@RemoteView
Philip Milne3f8956d2011-05-13 17:29:00 +0100151public class GridLayout extends ViewGroup {
152
153 // Public constants
154
155 /**
156 * The horizontal orientation.
157 */
158 public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
Philip Milneaa616f32011-05-27 18:38:01 -0700159
Philip Milne3f8956d2011-05-13 17:29:00 +0100160 /**
161 * The vertical orientation.
162 */
163 public static final int VERTICAL = LinearLayout.VERTICAL;
164
Philip Milneaa616f32011-05-27 18:38:01 -0700165 /**
166 * The constant used to indicate that a value is undefined.
167 * Fields can use this value to indicate that their values
168 * have not yet been set. Similarly, methods can return this value
169 * to indicate that there is no suitable value that the implementation
170 * can return.
171 * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
172 * intended to avoid confusion between valid values whose sign may not be known.
173 */
174 public static final int UNDEFINED = Integer.MIN_VALUE;
175
Philip Milne1e548252011-06-16 19:02:33 -0700176 /**
177 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
178 * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
179 * is made between the edges of each component's raw
180 * view boundary: i.e. the area delimited by the component's:
181 * {@link android.view.View#getTop() top},
182 * {@link android.view.View#getLeft() left},
183 * {@link android.view.View#getBottom() bottom} and
184 * {@link android.view.View#getRight() right} properties.
185 * <p>
186 * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode,
187 * children that belong to a row group that uses {@link #TOP} alignment will
188 * all return the same value when their {@link android.view.View#getTop()}
189 * method is called.
190 *
191 * @see #setAlignmentMode(int)
192 */
193 public static final int ALIGN_BOUNDS = 0;
194
195 /**
196 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
197 * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS},
198 * the bounds of each view are extended outwards, according
199 * to their margins, before the edges of the resulting rectangle are aligned.
200 * <p>
201 * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode,
202 * the quantity {@code top - layoutParams.topMargin} is the same for all children that
203 * belong to a row group that uses {@link #TOP} alignment.
204 *
205 * @see #setAlignmentMode(int)
206 */
207 public static final int ALIGN_MARGINS = 1;
208
Philip Milne3f8956d2011-05-13 17:29:00 +0100209 // Misc constants
210
Philip Milnef6679c82011-09-18 11:36:57 -0700211 static final String TAG = GridLayout.class.getName();
Philip Milnef6679c82011-09-18 11:36:57 -0700212 static final int MAX_SIZE = 100000;
213 static final int DEFAULT_CONTAINER_MARGIN = 0;
Philip Milned7dd8902012-01-26 16:55:30 -0800214 static final int UNINITIALIZED_HASH = 0;
Philip Milne3f8956d2011-05-13 17:29:00 +0100215
216 // Defaults
217
218 private static final int DEFAULT_ORIENTATION = HORIZONTAL;
219 private static final int DEFAULT_COUNT = UNDEFINED;
220 private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
Philip Milne899d5922011-07-21 11:39:37 -0700221 private static final boolean DEFAULT_ORDER_PRESERVED = true;
Philip Milne1e548252011-06-16 19:02:33 -0700222 private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
Philip Milne3f8956d2011-05-13 17:29:00 +0100223
224 // TypedArray indices
225
Philip Milneb0ce49b2011-07-15 15:39:07 -0700226 private static final int ORIENTATION = R.styleable.GridLayout_orientation;
227 private static final int ROW_COUNT = R.styleable.GridLayout_rowCount;
228 private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount;
229 private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins;
230 private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode;
231 private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved;
232 private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +0100233
234 // Instance variables
235
Philip Milnef6679c82011-09-18 11:36:57 -0700236 final Axis horizontalAxis = new Axis(true);
237 final Axis verticalAxis = new Axis(false);
Philip Milnef6679c82011-09-18 11:36:57 -0700238 int orientation = DEFAULT_ORIENTATION;
239 boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
240 int alignmentMode = DEFAULT_ALIGNMENT_MODE;
241 int defaultGap;
Philip Milned7dd8902012-01-26 16:55:30 -0800242 int lastLayoutParamsHashCode = UNINITIALIZED_HASH;
Philip Milneaa616f32011-05-27 18:38:01 -0700243
Philip Milne3f8956d2011-05-13 17:29:00 +0100244 // Constructors
245
246 /**
247 * {@inheritDoc}
248 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100249 public GridLayout(Context context, AttributeSet attrs, int defStyle) {
250 super(context, attrs, defStyle);
Philip Milnef6679c82011-09-18 11:36:57 -0700251 defaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
Philip Milneb0ce49b2011-07-15 15:39:07 -0700252 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout);
Philip Milne3f8956d2011-05-13 17:29:00 +0100253 try {
Philip Milne1e548252011-06-16 19:02:33 -0700254 setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
255 setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
Philip Milne5125e212011-07-21 11:39:37 -0700256 setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION));
257 setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS));
258 setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE));
Philip Milne3f8956d2011-05-13 17:29:00 +0100259 setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
260 setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
261 } finally {
262 a.recycle();
263 }
264 }
265
Philip Milne48b55242011-06-29 11:09:45 -0700266 /**
267 * {@inheritDoc}
268 */
269 public GridLayout(Context context, AttributeSet attrs) {
270 this(context, attrs, 0);
271 }
272
273 /**
274 * {@inheritDoc}
275 */
276 public GridLayout(Context context) {
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700277 //noinspection NullableProblems
Philip Milne48b55242011-06-29 11:09:45 -0700278 this(context, null);
279 }
280
Philip Milne3f8956d2011-05-13 17:29:00 +0100281 // Implementation
282
283 /**
284 * Returns the current orientation.
285 *
Philip Milne7fd94872011-06-07 20:14:17 -0700286 * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100287 *
288 * @see #setOrientation(int)
289 *
290 * @attr ref android.R.styleable#GridLayout_orientation
291 */
292 public int getOrientation() {
Philip Milnef6679c82011-09-18 11:36:57 -0700293 return orientation;
Philip Milne3f8956d2011-05-13 17:29:00 +0100294 }
295
296 /**
Philip Milneb65408f2012-05-21 10:44:46 -0700297 *
298 * GridLayout uses the orientation property for two purposes:
299 * <ul>
300 * <li>
301 * To control the 'direction' in which default row/column indices are generated
302 * when they are not specified in a component's layout parameters.
303 * </li>
304 * <li>
305 * To control which axis should be processed first during the layout operation:
306 * when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first.
307 * </li>
308 * </ul>
309 *
310 * The order in which axes are laid out is important if, for example, the height of
311 * one of GridLayout's children is dependent on its width - and its width is, in turn,
312 * dependent on the widths of other components.
313 * <p>
314 * If your layout contains a {@link TextView} (or derivative:
315 * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is
316 * in multi-line mode (the default) it is normally best to leave GridLayout's
317 * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of
318 * deriving its height for a given width, but not the other way around.
319 * <p>
320 * Other than the effects above, orientation does not affect the actual layout operation of
321 * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if
322 * the height of the intended layout greatly exceeds its width.
Philip Milne7fd94872011-06-07 20:14:17 -0700323 * <p>
324 * The default value of this property is {@link #HORIZONTAL}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100325 *
Philip Milne7fd94872011-06-07 20:14:17 -0700326 * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100327 *
328 * @see #getOrientation()
329 *
330 * @attr ref android.R.styleable#GridLayout_orientation
331 */
332 public void setOrientation(int orientation) {
Philip Milnef6679c82011-09-18 11:36:57 -0700333 if (this.orientation != orientation) {
334 this.orientation = orientation;
335 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100336 requestLayout();
337 }
338 }
339
340 /**
341 * Returns the current number of rows. This is either the last value that was set
342 * with {@link #setRowCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700343 * value of each the upper bounds defined in {@link LayoutParams#rowSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100344 *
345 * @return the current number of rows
346 *
347 * @see #setRowCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700348 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100349 *
350 * @attr ref android.R.styleable#GridLayout_rowCount
351 */
352 public int getRowCount() {
Philip Milnef6679c82011-09-18 11:36:57 -0700353 return verticalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100354 }
355
356 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700357 * RowCount is used only to generate default row/column indices when
358 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100359 *
Philip Milne7fd94872011-06-07 20:14:17 -0700360 * @param rowCount the number of rows
Philip Milne3f8956d2011-05-13 17:29:00 +0100361 *
362 * @see #getRowCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700363 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100364 *
365 * @attr ref android.R.styleable#GridLayout_rowCount
366 */
367 public void setRowCount(int rowCount) {
Philip Milnef6679c82011-09-18 11:36:57 -0700368 verticalAxis.setCount(rowCount);
369 invalidateStructure();
370 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100371 }
372
373 /**
374 * Returns the current number of columns. This is either the last value that was set
375 * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700376 * value of each the upper bounds defined in {@link LayoutParams#columnSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100377 *
378 * @return the current number of columns
379 *
380 * @see #setColumnCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700381 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100382 *
383 * @attr ref android.R.styleable#GridLayout_columnCount
384 */
385 public int getColumnCount() {
Philip Milnef6679c82011-09-18 11:36:57 -0700386 return horizontalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100387 }
388
389 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700390 * ColumnCount is used only to generate default column/column indices when
391 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100392 *
393 * @param columnCount the number of columns.
394 *
395 * @see #getColumnCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700396 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100397 *
398 * @attr ref android.R.styleable#GridLayout_columnCount
399 */
400 public void setColumnCount(int columnCount) {
Philip Milnef6679c82011-09-18 11:36:57 -0700401 horizontalAxis.setCount(columnCount);
402 invalidateStructure();
403 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100404 }
405
406 /**
407 * Returns whether or not this GridLayout will allocate default margins when no
408 * corresponding layout parameters are defined.
409 *
Philip Milne7fd94872011-06-07 20:14:17 -0700410 * @return {@code true} if default margins should be allocated
Philip Milne3f8956d2011-05-13 17:29:00 +0100411 *
412 * @see #setUseDefaultMargins(boolean)
413 *
414 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
415 */
416 public boolean getUseDefaultMargins() {
Philip Milnef6679c82011-09-18 11:36:57 -0700417 return useDefaultMargins;
Philip Milne3f8956d2011-05-13 17:29:00 +0100418 }
419
420 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700421 * When {@code true}, GridLayout allocates default margins around children
Philip Milne3f8956d2011-05-13 17:29:00 +0100422 * based on the child's visual characteristics. Each of the
423 * margins so defined may be independently overridden by an assignment
424 * to the appropriate layout parameter.
425 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700426 * When {@code false}, the default value of all margins is zero.
Philip Milneaa616f32011-05-27 18:38:01 -0700427 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700428 * When setting to {@code true}, consider setting the value of the
Philip Milne1e548252011-06-16 19:02:33 -0700429 * {@link #setAlignmentMode(int) alignmentMode}
430 * property to {@link #ALIGN_BOUNDS}.
Philip Milne7fd94872011-06-07 20:14:17 -0700431 * <p>
432 * The default value of this property is {@code false}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100433 *
Philip Milne7fd94872011-06-07 20:14:17 -0700434 * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
Philip Milne3f8956d2011-05-13 17:29:00 +0100435 *
436 * @see #getUseDefaultMargins()
Philip Milne1e548252011-06-16 19:02:33 -0700437 * @see #setAlignmentMode(int)
Philip Milne3f8956d2011-05-13 17:29:00 +0100438 *
439 * @see MarginLayoutParams#leftMargin
440 * @see MarginLayoutParams#topMargin
441 * @see MarginLayoutParams#rightMargin
442 * @see MarginLayoutParams#bottomMargin
443 *
444 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
445 */
446 public void setUseDefaultMargins(boolean useDefaultMargins) {
Philip Milnef6679c82011-09-18 11:36:57 -0700447 this.useDefaultMargins = useDefaultMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700448 requestLayout();
449 }
450
451 /**
Philip Milne1e548252011-06-16 19:02:33 -0700452 * Returns the alignment mode.
Philip Milneaa616f32011-05-27 18:38:01 -0700453 *
Philip Milne1e548252011-06-16 19:02:33 -0700454 * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700455 *
Philip Milne1e548252011-06-16 19:02:33 -0700456 * @see #ALIGN_BOUNDS
457 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700458 *
Philip Milne1e548252011-06-16 19:02:33 -0700459 * @see #setAlignmentMode(int)
460 *
461 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700462 */
Philip Milne1e548252011-06-16 19:02:33 -0700463 public int getAlignmentMode() {
Philip Milnef6679c82011-09-18 11:36:57 -0700464 return alignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700465 }
466
467 /**
Philip Milne1e548252011-06-16 19:02:33 -0700468 * Sets the alignment mode to be used for all of the alignments between the
469 * children of this container.
Philip Milne7fd94872011-06-07 20:14:17 -0700470 * <p>
Philip Milne1e548252011-06-16 19:02:33 -0700471 * The default value of this property is {@link #ALIGN_MARGINS}.
Philip Milneaa616f32011-05-27 18:38:01 -0700472 *
Philip Milne1e548252011-06-16 19:02:33 -0700473 * @param alignmentMode 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 #getAlignmentMode()
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 void setAlignmentMode(int alignmentMode) {
Philip Milnef6679c82011-09-18 11:36:57 -0700483 this.alignmentMode = alignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700484 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100485 }
486
487 /**
488 * Returns whether or not row boundaries are ordered by their grid indices.
489 *
Philip Milne7fd94872011-06-07 20:14:17 -0700490 * @return {@code true} if row boundaries must appear in the order of their indices,
491 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100492 *
493 * @see #setRowOrderPreserved(boolean)
494 *
495 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
496 */
497 public boolean isRowOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -0700498 return verticalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100499 }
500
501 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700502 * When this property is {@code true}, GridLayout is forced to place the row boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700503 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100504 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700505 * When this property is {@code false} GridLayout is at liberty to place the vertical row
506 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700507 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700508 * The default value of this property is {@code true}.
Philip Milne7fd94872011-06-07 20:14:17 -0700509
510 * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
511 * of row boundaries
Philip Milne3f8956d2011-05-13 17:29:00 +0100512 *
513 * @see #isRowOrderPreserved()
514 *
515 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
516 */
517 public void setRowOrderPreserved(boolean rowOrderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -0700518 verticalAxis.setOrderPreserved(rowOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700519 invalidateStructure();
520 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100521 }
522
523 /**
524 * Returns whether or not column boundaries are ordered by their grid indices.
525 *
Philip Milne7fd94872011-06-07 20:14:17 -0700526 * @return {@code true} if column boundaries must appear in the order of their indices,
527 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100528 *
529 * @see #setColumnOrderPreserved(boolean)
530 *
531 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
532 */
533 public boolean isColumnOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -0700534 return horizontalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100535 }
536
537 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700538 * When this property is {@code true}, GridLayout is forced to place the column boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700539 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100540 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700541 * When this property is {@code false} GridLayout is at liberty to place the horizontal column
542 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700543 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700544 * The default value of this property is {@code true}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100545 *
Philip Milne7fd94872011-06-07 20:14:17 -0700546 * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
Philip Milne3f8956d2011-05-13 17:29:00 +0100547 * of column boundaries.
548 *
549 * @see #isColumnOrderPreserved()
550 *
551 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
552 */
553 public void setColumnOrderPreserved(boolean columnOrderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -0700554 horizontalAxis.setOrderPreserved(columnOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700555 invalidateStructure();
556 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100557 }
558
Philip Milne5125e212011-07-21 11:39:37 -0700559 // Static utility methods
560
Philip Milnef6679c82011-09-18 11:36:57 -0700561 static int max2(int[] a, int valueIfEmpty) {
Philip Milne51f17d52011-06-13 10:44:49 -0700562 int result = valueIfEmpty;
563 for (int i = 0, N = a.length; i < N; i++) {
564 result = Math.max(result, a[i]);
565 }
566 return result;
567 }
568
Philip Milne899d5922011-07-21 11:39:37 -0700569 @SuppressWarnings("unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -0700570 static <T> T[] append(T[] a, T[] b) {
Philip Milne48b55242011-06-29 11:09:45 -0700571 T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
572 System.arraycopy(a, 0, result, 0, a.length);
573 System.arraycopy(b, 0, result, a.length, b.length);
Philip Milne3f8956d2011-05-13 17:29:00 +0100574 return result;
575 }
576
Philip Milnef6679c82011-09-18 11:36:57 -0700577 static Alignment getAlignment(int gravity, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -0700578 int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
579 int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
580 int flags = (gravity & mask) >> shift;
581 switch (flags) {
582 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
Philip Milne1557fd72012-04-04 23:41:34 -0700583 return horizontal ? LEFT : TOP;
Philip Milne5125e212011-07-21 11:39:37 -0700584 case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
Philip Milne1557fd72012-04-04 23:41:34 -0700585 return horizontal ? RIGHT : BOTTOM;
Philip Milne5125e212011-07-21 11:39:37 -0700586 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
587 return FILL;
588 case AXIS_SPECIFIED:
589 return CENTER;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800590 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION):
591 return START;
592 case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION):
593 return END;
Philip Milne5125e212011-07-21 11:39:37 -0700594 default:
595 return UNDEFINED_ALIGNMENT;
596 }
597 }
598
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700599 /** @noinspection UnusedParameters*/
Philip Milne1fd16372011-06-21 14:57:47 -0700600 private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
Philip Milne899d5922011-07-21 11:39:37 -0700601 if (c.getClass() == Space.class) {
602 return 0;
603 }
Philip Milnef6679c82011-09-18 11:36:57 -0700604 return defaultGap / 2;
Philip Milne3f8956d2011-05-13 17:29:00 +0100605 }
606
Philip Milne1fd16372011-06-21 14:57:47 -0700607 private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
Philip Milne7b757812012-09-19 18:13:44 -0700608 return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100609 }
610
Philip Milne7a23b492012-04-24 22:12:36 -0700611 private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
Philip Milnef6679c82011-09-18 11:36:57 -0700612 if (!useDefaultMargins) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100613 return 0;
614 }
Philip Milne93cd6a62011-07-12 14:49:45 -0700615 Spec spec = horizontal ? p.columnSpec : p.rowSpec;
Philip Milnef6679c82011-09-18 11:36:57 -0700616 Axis axis = horizontal ? horizontalAxis : verticalAxis;
Philip Milne93cd6a62011-07-12 14:49:45 -0700617 Interval span = spec.span;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800618 boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading;
619 boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
Philip Milne3f8956d2011-05-13 17:29:00 +0100620
Philip Milne1fd16372011-06-21 14:57:47 -0700621 return getDefaultMargin(c, isAtEdge, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100622 }
623
Philip Milnef6679c82011-09-18 11:36:57 -0700624 int getMargin1(View view, boolean horizontal, boolean leading) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100625 LayoutParams lp = getLayoutParams(view);
626 int margin = horizontal ?
Philip Milneaa616f32011-05-27 18:38:01 -0700627 (leading ? lp.leftMargin : lp.rightMargin) :
628 (leading ? lp.topMargin : lp.bottomMargin);
Philip Milne7a23b492012-04-24 22:12:36 -0700629 return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin;
Philip Milne1fd16372011-06-21 14:57:47 -0700630 }
631
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700632 private int getMargin(View view, boolean horizontal, boolean leading) {
Philip Milnef6679c82011-09-18 11:36:57 -0700633 if (alignmentMode == ALIGN_MARGINS) {
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700634 return getMargin1(view, horizontal, leading);
635 } else {
Philip Milnef6679c82011-09-18 11:36:57 -0700636 Axis axis = horizontal ? horizontalAxis : verticalAxis;
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700637 int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
638 LayoutParams lp = getLayoutParams(view);
639 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
640 int index = leading ? spec.span.min : spec.span.max;
641 return margins[index];
642 }
643 }
644
Philip Milne1fd16372011-06-21 14:57:47 -0700645 private int getTotalMargin(View child, boolean horizontal) {
646 return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
Philip Milne3f8956d2011-05-13 17:29:00 +0100647 }
648
Philip Milne899d5922011-07-21 11:39:37 -0700649 private static boolean fits(int[] a, int value, int start, int end) {
650 if (end > a.length) {
651 return false;
652 }
653 for (int i = start; i < end; i++) {
654 if (a[i] > value) {
655 return false;
656 }
657 }
658 return true;
659 }
660
661 private static void procrusteanFill(int[] a, int start, int end, int value) {
662 int length = a.length;
663 Arrays.fill(a, Math.min(start, length), Math.min(end, length), value);
664 }
665
666 private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) {
667 lp.setRowSpecSpan(new Interval(row, row + rowSpan));
668 lp.setColumnSpecSpan(new Interval(col, col + colSpan));
669 }
670
671 // Logic to avert infinite loops by ensuring that the cells can be placed somewhere.
672 private static int clip(Interval minorRange, boolean minorWasDefined, int count) {
673 int size = minorRange.size();
674 if (count == 0) {
675 return size;
676 }
677 int min = minorWasDefined ? min(minorRange.min, count) : 0;
678 return min(size, count - min);
679 }
680
Philip Milnef4748702011-06-09 18:30:32 -0700681 // install default indices for cells that don't define them
Philip Milne3f8956d2011-05-13 17:29:00 +0100682 private void validateLayoutParams() {
Philip Milnef6679c82011-09-18 11:36:57 -0700683 final boolean horizontal = (orientation == HORIZONTAL);
684 final Axis axis = horizontal ? horizontalAxis : verticalAxis;
Jim Miller782c04b2012-05-02 15:45:23 -0700685 final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
Philip Milne3f8956d2011-05-13 17:29:00 +0100686
Philip Milne899d5922011-07-21 11:39:37 -0700687 int major = 0;
688 int minor = 0;
689 int[] maxSizes = new int[count];
690
691 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milned7dd8902012-01-26 16:55:30 -0800692 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
Philip Milne899d5922011-07-21 11:39:37 -0700693
Philip Milnef6679c82011-09-18 11:36:57 -0700694 final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
695 final Interval majorRange = majorSpec.span;
696 final boolean majorWasDefined = majorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700697 final int majorSpan = majorRange.size();
698 if (majorWasDefined) {
699 major = majorRange.min;
Philip Milnef4748702011-06-09 18:30:32 -0700700 }
701
Philip Milnef6679c82011-09-18 11:36:57 -0700702 final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
703 final Interval minorRange = minorSpec.span;
704 final boolean minorWasDefined = minorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700705 final int minorSpan = clip(minorRange, minorWasDefined, count);
706 if (minorWasDefined) {
707 minor = minorRange.min;
708 }
Philip Milnef4748702011-06-09 18:30:32 -0700709
Philip Milne899d5922011-07-21 11:39:37 -0700710 if (count != 0) {
711 // Find suitable row/col values when at least one is undefined.
712 if (!majorWasDefined || !minorWasDefined) {
713 while (!fits(maxSizes, major, minor, minor + minorSpan)) {
714 if (minorWasDefined) {
715 major++;
716 } else {
717 if (minor + minorSpan <= count) {
718 minor++;
719 } else {
720 minor = 0;
721 major++;
722 }
Philip Milnef4748702011-06-09 18:30:32 -0700723 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100724 }
725 }
Philip Milne899d5922011-07-21 11:39:37 -0700726 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan);
Philip Milne3f8956d2011-05-13 17:29:00 +0100727 }
Philip Milne899d5922011-07-21 11:39:37 -0700728
729 if (horizontal) {
730 setCellGroup(lp, major, majorSpan, minor, minorSpan);
731 } else {
732 setCellGroup(lp, minor, minorSpan, major, majorSpan);
733 }
734
735 minor = minor + minorSpan;
736 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100737 }
738
739 private void invalidateStructure() {
Philip Milneedd69512012-03-14 17:21:33 -0700740 lastLayoutParamsHashCode = UNINITIALIZED_HASH;
Philip Milnef6679c82011-09-18 11:36:57 -0700741 horizontalAxis.invalidateStructure();
742 verticalAxis.invalidateStructure();
Philip Milne899d5922011-07-21 11:39:37 -0700743 // This can end up being done twice. Better twice than not at all.
Philip Milne3f8956d2011-05-13 17:29:00 +0100744 invalidateValues();
745 }
746
747 private void invalidateValues() {
Philip Milneaa616f32011-05-27 18:38:01 -0700748 // Need null check because requestLayout() is called in View's initializer,
749 // before we are set up.
Philip Milnef6679c82011-09-18 11:36:57 -0700750 if (horizontalAxis != null && verticalAxis != null) {
751 horizontalAxis.invalidateValues();
752 verticalAxis.invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -0700753 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100754 }
755
Philip Milned7dd8902012-01-26 16:55:30 -0800756 /** @hide */
757 @Override
758 protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) {
759 super.onSetLayoutParams(child, layoutParams);
Philip Milne0f57cea2012-05-12 09:34:25 -0700760
761 if (!checkLayoutParams(layoutParams)) {
762 handleInvalidParams("supplied LayoutParams are of the wrong type");
763 }
764
Philip Milned7dd8902012-01-26 16:55:30 -0800765 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100766 }
767
Philip Milnef6679c82011-09-18 11:36:57 -0700768 final LayoutParams getLayoutParams(View c) {
Philip Milned7dd8902012-01-26 16:55:30 -0800769 return (LayoutParams) c.getLayoutParams();
Philip Milne3f8956d2011-05-13 17:29:00 +0100770 }
771
Philip Milne0f57cea2012-05-12 09:34:25 -0700772 private static void handleInvalidParams(String msg) {
773 throw new IllegalArgumentException(msg + ". ");
774 }
775
776 private void checkLayoutParams(LayoutParams lp, boolean horizontal) {
777 String groupName = horizontal ? "column" : "row";
778 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
779 Interval span = spec.span;
780 if (span.min != UNDEFINED && span.min < 0) {
781 handleInvalidParams(groupName + " indices must be positive");
782 }
783 Axis axis = horizontal ? horizontalAxis : verticalAxis;
784 int count = axis.definedCount;
785 if (count != UNDEFINED) {
786 if (span.max > count) {
787 handleInvalidParams(groupName +
788 " indices (start + span) mustn't exceed the " + groupName + " count");
789 }
790 if (span.size() > count) {
791 handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count");
792 }
793 }
794 }
795
796 @Override
797 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
798 if (!(p instanceof LayoutParams)) {
799 return false;
800 }
801 LayoutParams lp = (LayoutParams) p;
802
803 checkLayoutParams(lp, true);
804 checkLayoutParams(lp, false);
805
806 return true;
807 }
808
Philip Milne3f8956d2011-05-13 17:29:00 +0100809 @Override
810 protected LayoutParams generateDefaultLayoutParams() {
811 return new LayoutParams();
812 }
813
814 @Override
815 public LayoutParams generateLayoutParams(AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -0700816 return new LayoutParams(getContext(), attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +0100817 }
818
819 @Override
820 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
821 return new LayoutParams(p);
822 }
823
824 // Draw grid
825
826 private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800827 if (isLayoutRtl()) {
828 int width = getWidth();
Philip Milne7b757812012-09-19 18:13:44 -0700829 graphics.drawLine(width - x1, y1, width - x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800830 } else {
Philip Milne7b757812012-09-19 18:13:44 -0700831 graphics.drawLine(x1, y1, x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800832 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100833 }
834
Philip Milne10ca24a2012-04-23 15:38:27 -0700835 /**
836 * @hide
837 */
838 @Override
Philip Milne7b757812012-09-19 18:13:44 -0700839 protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700840 // Apply defaults, so as to remove UNDEFINED values
841 LayoutParams lp = new LayoutParams();
842 for (int i = 0; i < getChildCount(); i++) {
843 View c = getChildAt(i);
844 lp.setMargins(
Philip Milne7b757812012-09-19 18:13:44 -0700845 getMargin1(c, true, true),
846 getMargin1(c, false, true),
847 getMargin1(c, true, false),
848 getMargin1(c, false, false));
849 lp.onDebugDraw(c, canvas, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700850 }
Philip Milneb5599762011-08-05 11:04:36 -0700851 }
852
Philip Milne10ca24a2012-04-23 15:38:27 -0700853 /**
854 * @hide
855 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100856 @Override
Philip Milne10ca24a2012-04-23 15:38:27 -0700857 protected void onDebugDraw(Canvas canvas) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700858 Paint paint = new Paint();
859 paint.setStyle(Paint.Style.STROKE);
860 paint.setColor(Color.argb(50, 255, 255, 255));
Philip Milne3f8956d2011-05-13 17:29:00 +0100861
Philip Milne7b757812012-09-19 18:13:44 -0700862 Insets insets = getOpticalInsets();
863
864 int top = getPaddingTop() + insets.top;
865 int left = getPaddingLeft() + insets.left;
866 int right = getWidth() - getPaddingRight() - insets.right;
867 int bottom = getHeight() - getPaddingBottom() - insets.bottom;
868
Philip Milne10ca24a2012-04-23 15:38:27 -0700869 int[] xs = horizontalAxis.locations;
870 if (xs != null) {
871 for (int i = 0, length = xs.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700872 int x = left + xs[i];
873 drawLine(canvas, x, top, x, bottom, paint);
Philip Milne3f8956d2011-05-13 17:29:00 +0100874 }
875 }
Philip Milne10ca24a2012-04-23 15:38:27 -0700876
877 int[] ys = verticalAxis.locations;
878 if (ys != null) {
879 for (int i = 0, length = ys.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700880 int y = top + ys[i];
881 drawLine(canvas, left, y, right, y, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700882 }
883 }
884
885 super.onDebugDraw(canvas);
Philip Milne3f8956d2011-05-13 17:29:00 +0100886 }
887
Philip Milne3f8956d2011-05-13 17:29:00 +0100888 // Add/remove
889
Philip Milnee7dda0b2011-07-19 10:35:56 -0700890 /**
891 * @hide
892 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100893 @Override
Philip Milnef51d91c2011-07-18 16:12:19 -0700894 protected void onViewAdded(View child) {
895 super.onViewAdded(child);
Philip Milne3f8956d2011-05-13 17:29:00 +0100896 invalidateStructure();
897 }
898
Philip Milnee7dda0b2011-07-19 10:35:56 -0700899 /**
900 * @hide
901 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100902 @Override
Philip Milnef51d91c2011-07-18 16:12:19 -0700903 protected void onViewRemoved(View child) {
904 super.onViewRemoved(child);
Philip Milneb0ce49b2011-07-15 15:39:07 -0700905 invalidateStructure();
906 }
907
Philip Milne350f0a62011-07-19 14:00:56 -0700908 /**
909 * We need to call invalidateStructure() when a child's GONE flag changes state.
910 * This implementation is a catch-all, invalidating on any change in the visibility flags.
911 *
912 * @hide
913 */
914 @Override
Chet Haase0d299362012-01-26 10:51:48 -0800915 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
916 super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
917 if (oldVisibility == GONE || newVisibility == GONE) {
918 invalidateStructure();
919 }
Philip Milne350f0a62011-07-19 14:00:56 -0700920 }
921
Philip Milned7dd8902012-01-26 16:55:30 -0800922 private int computeLayoutParamsHashCode() {
923 int result = 1;
924 for (int i = 0, N = getChildCount(); i < N; i++) {
925 View c = getChildAt(i);
926 if (c.getVisibility() == View.GONE) continue;
927 LayoutParams lp = (LayoutParams) c.getLayoutParams();
928 result = 31 * result + lp.hashCode();
929 }
930 return result;
Philip Milneb3a8c542011-06-20 16:02:59 -0700931 }
932
Philip Milneedd69512012-03-14 17:21:33 -0700933 private void consistencyCheck() {
934 if (lastLayoutParamsHashCode == UNINITIALIZED_HASH) {
935 validateLayoutParams();
936 lastLayoutParamsHashCode = computeLayoutParamsHashCode();
937 } else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
Philip Milned7dd8902012-01-26 16:55:30 -0800938 Log.w(TAG, "The fields of some layout parameters were modified in between layout " +
939 "operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
Philip Milneedd69512012-03-14 17:21:33 -0700940 invalidateStructure();
941 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -0800942 }
943 }
944
945 // Measurement
946
Philip Milnee0b85cd2013-04-12 14:38:34 -0700947 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -0700948 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
Philip Milneedd69512012-03-14 17:21:33 -0700949 int childWidth, int childHeight) {
Philip Milne4a145d72011-09-29 14:14:25 -0700950 int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -0700951 getTotalMargin(child, true), childWidth);
Philip Milne4a145d72011-09-29 14:14:25 -0700952 int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -0700953 getTotalMargin(child, false), childHeight);
Philip Milne4a145d72011-09-29 14:14:25 -0700954 child.measure(childWidthSpec, childHeightSpec);
Philip Milneb3a8c542011-06-20 16:02:59 -0700955 }
956
Philip Milnee0b85cd2013-04-12 14:38:34 -0700957 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -0700958 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
Philip Milneb3a8c542011-06-20 16:02:59 -0700959 for (int i = 0, N = getChildCount(); i < N; i++) {
960 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -0800961 if (c.getVisibility() == View.GONE) continue;
Philip Milne4a145d72011-09-29 14:14:25 -0700962 LayoutParams lp = getLayoutParams(c);
963 if (firstPass) {
964 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
965 } else {
Philip Milneecab1172011-10-25 15:07:19 -0700966 boolean horizontal = (orientation == HORIZONTAL);
967 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -0700968 if (spec.alignment == FILL) {
969 Interval span = spec.span;
Philip Milneecab1172011-10-25 15:07:19 -0700970 Axis axis = horizontal ? horizontalAxis : verticalAxis;
Philip Milne4a145d72011-09-29 14:14:25 -0700971 int[] locations = axis.getLocations();
Philip Milneecab1172011-10-25 15:07:19 -0700972 int cellSize = locations[span.max] - locations[span.min];
973 int viewSize = cellSize - getTotalMargin(c, horizontal);
974 if (horizontal) {
975 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);
Philip Milne4a145d72011-09-29 14:14:25 -0700976 } else {
Philip Milneecab1172011-10-25 15:07:19 -0700977 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);
Philip Milne4a145d72011-09-29 14:14:25 -0700978 }
979 }
980 }
Philip Milneb3a8c542011-06-20 16:02:59 -0700981 }
982 }
983
Philip Milnee0b85cd2013-04-12 14:38:34 -0700984 static int adjust(int measureSpec, int delta) {
985 return makeMeasureSpec(
986 MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec));
987 }
988
Philip Milne3f8956d2011-05-13 17:29:00 +0100989 @Override
990 protected void onMeasure(int widthSpec, int heightSpec) {
Philip Milneedd69512012-03-14 17:21:33 -0700991 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -0800992
Philip Milne4a145d72011-09-29 14:14:25 -0700993 /** If we have been called by {@link View#measure(int, int)}, one of width or height
994 * is likely to have changed. We must invalidate if so. */
995 invalidateValues();
Philip Milne3f8956d2011-05-13 17:29:00 +0100996
Philip Milnee0b85cd2013-04-12 14:38:34 -0700997 int hPadding = getPaddingLeft() + getPaddingRight();
998 int vPadding = getPaddingTop() + getPaddingBottom();
Philip Milne3f8956d2011-05-13 17:29:00 +0100999
Philip Milnee0b85cd2013-04-12 14:38:34 -07001000 int widthSpecSansPadding = adjust( widthSpec, -hPadding);
1001 int heightSpecSansPadding = adjust(heightSpec, -vPadding);
1002
1003 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);
1004
1005 int widthSansPadding;
1006 int heightSansPadding;
Philip Milne4a145d72011-09-29 14:14:25 -07001007
1008 // Use the orientation property to decide which axis should be laid out first.
1009 if (orientation == HORIZONTAL) {
Philip Milnee0b85cd2013-04-12 14:38:34 -07001010 widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
1011 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
1012 heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001013 } else {
Philip Milnee0b85cd2013-04-12 14:38:34 -07001014 heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
1015 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
1016 widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001017 }
1018
Philip Milnee0b85cd2013-04-12 14:38:34 -07001019 int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth());
1020 int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight());
Philip Milne09e2d4d2011-06-20 10:28:18 -07001021
Philip Milne3f8956d2011-05-13 17:29:00 +01001022 setMeasuredDimension(
Philip Milnee0b85cd2013-04-12 14:38:34 -07001023 resolveSizeAndState(measuredWidth, widthSpec, 0),
Philip Milne09e2d4d2011-06-20 10:28:18 -07001024 resolveSizeAndState(measuredHeight, heightSpec, 0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001025 }
1026
Philip Milne48b55242011-06-29 11:09:45 -07001027 private int getMeasurement(View c, boolean horizontal) {
Philip Milne7b757812012-09-19 18:13:44 -07001028 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
Philip Milneaa616f32011-05-27 18:38:01 -07001029 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001030
Philip Milnef6679c82011-09-18 11:36:57 -07001031 final int getMeasurementIncludingMargin(View c, boolean horizontal) {
Philip Milned7dd8902012-01-26 16:55:30 -08001032 if (c.getVisibility() == View.GONE) {
Philip Milne5125e212011-07-21 11:39:37 -07001033 return 0;
1034 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001035 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
Philip Milne3f8956d2011-05-13 17:29:00 +01001036 }
1037
Philip Milne3f8956d2011-05-13 17:29:00 +01001038 @Override
Philip Milneaa616f32011-05-27 18:38:01 -07001039 public void requestLayout() {
1040 super.requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +01001041 invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -07001042 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001043
Philip Milnef6679c82011-09-18 11:36:57 -07001044 final Alignment getAlignment(Alignment alignment, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -07001045 return (alignment != UNDEFINED_ALIGNMENT) ? alignment :
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08001046 (horizontal ? START : BASELINE);
Philip Milne5125e212011-07-21 11:39:37 -07001047 }
1048
Philip Milneaa616f32011-05-27 18:38:01 -07001049 // Layout container
1050
1051 /**
1052 * {@inheritDoc}
1053 */
1054 /*
1055 The layout operation is implemented by delegating the heavy lifting to the
1056 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
1057 Together they compute the locations of the vertical and horizontal lines of
1058 the grid (respectively!).
1059
1060 This method is then left with the simpler task of applying margins, gravity
1061 and sizing to each child view and then placing it in its cell.
1062 */
1063 @Override
Philip Milne09e2d4d2011-06-20 10:28:18 -07001064 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Philip Milneedd69512012-03-14 17:21:33 -07001065 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001066
Philip Milne09e2d4d2011-06-20 10:28:18 -07001067 int targetWidth = right - left;
1068 int targetHeight = bottom - top;
Philip Milne3f8956d2011-05-13 17:29:00 +01001069
1070 int paddingLeft = getPaddingLeft();
1071 int paddingTop = getPaddingTop();
1072 int paddingRight = getPaddingRight();
1073 int paddingBottom = getPaddingBottom();
1074
Philip Milnef6679c82011-09-18 11:36:57 -07001075 horizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
1076 verticalAxis.layout(targetHeight - paddingTop - paddingBottom);
Philip Milne3f8956d2011-05-13 17:29:00 +01001077
Philip Milnef6679c82011-09-18 11:36:57 -07001078 int[] hLocations = horizontalAxis.getLocations();
1079 int[] vLocations = verticalAxis.getLocations();
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001080
Philip Milneb3a8c542011-06-20 16:02:59 -07001081 for (int i = 0, N = getChildCount(); i < N; i++) {
1082 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001083 if (c.getVisibility() == View.GONE) continue;
Philip Milneb3a8c542011-06-20 16:02:59 -07001084 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001085 Spec columnSpec = lp.columnSpec;
1086 Spec rowSpec = lp.rowSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001087
Philip Milne93cd6a62011-07-12 14:49:45 -07001088 Interval colSpan = columnSpec.span;
1089 Interval rowSpan = rowSpec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001090
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001091 int x1 = hLocations[colSpan.min];
1092 int y1 = vLocations[rowSpan.min];
Philip Milneaa616f32011-05-27 18:38:01 -07001093
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001094 int x2 = hLocations[colSpan.max];
1095 int y2 = vLocations[rowSpan.max];
Philip Milne3f8956d2011-05-13 17:29:00 +01001096
1097 int cellWidth = x2 - x1;
1098 int cellHeight = y2 - y1;
1099
Philip Milne48b55242011-06-29 11:09:45 -07001100 int pWidth = getMeasurement(c, true);
1101 int pHeight = getMeasurement(c, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001102
Philip Milne5125e212011-07-21 11:39:37 -07001103 Alignment hAlign = getAlignment(columnSpec.alignment, true);
1104 Alignment vAlign = getAlignment(rowSpec.alignment, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001105
Philip Milne6216e872012-02-16 17:15:50 -08001106 Bounds boundsX = horizontalAxis.getGroupBounds().getValue(i);
1107 Bounds boundsY = verticalAxis.getGroupBounds().getValue(i);
Philip Milne7fd94872011-06-07 20:14:17 -07001108
1109 // Gravity offsets: the location of the alignment group relative to its cell group.
Philip Milne6216e872012-02-16 17:15:50 -08001110 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true));
1111 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true));
Philip Milne7fd94872011-06-07 20:14:17 -07001112
Philip Milne6216e872012-02-16 17:15:50 -08001113 int leftMargin = getMargin(c, true, true);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001114 int topMargin = getMargin(c, false, true);
Philip Milne6216e872012-02-16 17:15:50 -08001115 int rightMargin = getMargin(c, true, false);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001116 int bottomMargin = getMargin(c, false, false);
Philip Milne7fd94872011-06-07 20:14:17 -07001117
Philip Milne1557fd72012-04-04 23:41:34 -07001118 int sumMarginsX = leftMargin + rightMargin;
1119 int sumMarginsY = topMargin + bottomMargin;
Philip Milne7fd94872011-06-07 20:14:17 -07001120
Philip Milne1557fd72012-04-04 23:41:34 -07001121 // Alignment offsets: the location of the view relative to its alignment group.
1122 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
1123 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
1124
1125 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
1126 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
Philip Milne7fd94872011-06-07 20:14:17 -07001127
Philip Milne6216e872012-02-16 17:15:50 -08001128 int dx = x1 + gravityOffsetX + alignmentOffsetX;
Philip Milne3f8956d2011-05-13 17:29:00 +01001129
Philip Milne6216e872012-02-16 17:15:50 -08001130 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx :
1131 targetWidth - width - paddingRight - rightMargin - dx;
1132 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
Philip Milne3f8956d2011-05-13 17:29:00 +01001133
Philip Milne899d5922011-07-21 11:39:37 -07001134 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
1135 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
1136 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001137 c.layout(cx, cy, cx + width, cy + height);
Philip Milne3f8956d2011-05-13 17:29:00 +01001138 }
1139 }
1140
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001141 @Override
1142 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1143 super.onInitializeAccessibilityEvent(event);
1144 event.setClassName(GridLayout.class.getName());
1145 }
1146
1147 @Override
1148 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1149 super.onInitializeAccessibilityNodeInfo(info);
1150 info.setClassName(GridLayout.class.getName());
1151 }
1152
Philip Milne3f8956d2011-05-13 17:29:00 +01001153 // Inner classes
1154
Philip Milneaa616f32011-05-27 18:38:01 -07001155 /*
1156 This internal class houses the algorithm for computing the locations of grid lines;
1157 along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
1158 distinguished by the "horizontal" flag which is true for the horizontal axis and false
1159 for the vertical one.
1160 */
Philip Milnef6679c82011-09-18 11:36:57 -07001161 final class Axis {
Philip Milne48b55242011-06-29 11:09:45 -07001162 private static final int NEW = 0;
Philip Milne3f8956d2011-05-13 17:29:00 +01001163 private static final int PENDING = 1;
1164 private static final int COMPLETE = 2;
1165
1166 public final boolean horizontal;
1167
Philip Milnef6679c82011-09-18 11:36:57 -07001168 public int definedCount = UNDEFINED;
Philip Milne4a145d72011-09-29 14:14:25 -07001169 private int maxIndex = UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001170
Philip Milne93cd6a62011-07-12 14:49:45 -07001171 PackedMap<Spec, Bounds> groupBounds;
Philip Milne3f8956d2011-05-13 17:29:00 +01001172 public boolean groupBoundsValid = false;
1173
Philip Milne48b55242011-06-29 11:09:45 -07001174 PackedMap<Interval, MutableInt> forwardLinks;
1175 public boolean forwardLinksValid = false;
1176
1177 PackedMap<Interval, MutableInt> backwardLinks;
1178 public boolean backwardLinksValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001179
Philip Milne3f8956d2011-05-13 17:29:00 +01001180 public int[] leadingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001181 public boolean leadingMarginsValid = false;
1182
Philip Milne3f8956d2011-05-13 17:29:00 +01001183 public int[] trailingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001184 public boolean trailingMarginsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001185
1186 public Arc[] arcs;
1187 public boolean arcsValid = false;
1188
Philip Milneaa616f32011-05-27 18:38:01 -07001189 public int[] locations;
Philip Milne48b55242011-06-29 11:09:45 -07001190 public boolean locationsValid = false;
Philip Milneaa616f32011-05-27 18:38:01 -07001191
Philip Milnef6679c82011-09-18 11:36:57 -07001192 boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001193
Philip Milne48b55242011-06-29 11:09:45 -07001194 private MutableInt parentMin = new MutableInt(0);
1195 private MutableInt parentMax = new MutableInt(-MAX_SIZE);
1196
Philip Milne3f8956d2011-05-13 17:29:00 +01001197 private Axis(boolean horizontal) {
1198 this.horizontal = horizontal;
1199 }
1200
Philip Milne4a145d72011-09-29 14:14:25 -07001201 private int calculateMaxIndex() {
1202 // the number Integer.MIN_VALUE + 1 comes up in undefined cells
1203 int result = -1;
Philip Milneb3a8c542011-06-20 16:02:59 -07001204 for (int i = 0, N = getChildCount(); i < N; i++) {
1205 View c = getChildAt(i);
Philip Milneb3a8c542011-06-20 16:02:59 -07001206 LayoutParams params = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001207 Spec spec = horizontal ? params.columnSpec : params.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -07001208 Interval span = spec.span;
1209 result = max(result, span.min);
1210 result = max(result, span.max);
Philip Milne0f57cea2012-05-12 09:34:25 -07001211 result = max(result, span.size());
Philip Milne3f8956d2011-05-13 17:29:00 +01001212 }
Philip Milne4a145d72011-09-29 14:14:25 -07001213 return result == -1 ? UNDEFINED : result;
Philip Milne3f8956d2011-05-13 17:29:00 +01001214 }
1215
Philip Milne4a145d72011-09-29 14:14:25 -07001216 private int getMaxIndex() {
1217 if (maxIndex == UNDEFINED) {
1218 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children
Philip Milne3f8956d2011-05-13 17:29:00 +01001219 }
Philip Milne4a145d72011-09-29 14:14:25 -07001220 return maxIndex;
Philip Milnef6679c82011-09-18 11:36:57 -07001221 }
1222
1223 public int getCount() {
Philip Milne4a145d72011-09-29 14:14:25 -07001224 return max(definedCount, getMaxIndex());
Philip Milne3f8956d2011-05-13 17:29:00 +01001225 }
1226
1227 public void setCount(int count) {
Philip Milne0f57cea2012-05-12 09:34:25 -07001228 if (count != UNDEFINED && count < getMaxIndex()) {
1229 handleInvalidParams((horizontal ? "column" : "row") +
1230 "Count must be greater than or equal to the maximum of all grid indices " +
1231 "(and spans) defined in the LayoutParams of each child");
1232 }
Philip Milnef6679c82011-09-18 11:36:57 -07001233 this.definedCount = count;
Philip Milne3f8956d2011-05-13 17:29:00 +01001234 }
1235
1236 public boolean isOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -07001237 return orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001238 }
1239
1240 public void setOrderPreserved(boolean orderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -07001241 this.orderPreserved = orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001242 invalidateStructure();
1243 }
1244
Philip Milne93cd6a62011-07-12 14:49:45 -07001245 private PackedMap<Spec, Bounds> createGroupBounds() {
1246 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class);
Philip Milne48b55242011-06-29 11:09:45 -07001247 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001248 View c = getChildAt(i);
Philip Milne5125e212011-07-21 11:39:37 -07001249 LayoutParams lp = getLayoutParams(c);
1250 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1251 Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds();
1252 assoc.put(spec, bounds);
Philip Milne3f8956d2011-05-13 17:29:00 +01001253 }
Philip Milne48b55242011-06-29 11:09:45 -07001254 return assoc.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001255 }
1256
1257 private void computeGroupBounds() {
Philip Milneb3a8c542011-06-20 16:02:59 -07001258 Bounds[] values = groupBounds.values;
1259 for (int i = 0; i < values.length; i++) {
1260 values[i].reset();
Philip Milne3f8956d2011-05-13 17:29:00 +01001261 }
Philip Milneaa616f32011-05-27 18:38:01 -07001262 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001263 View c = getChildAt(i);
1264 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001265 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Philip Milne1557fd72012-04-04 23:41:34 -07001266 groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
Philip Milne3f8956d2011-05-13 17:29:00 +01001267 }
1268 }
1269
Philip Milnef6679c82011-09-18 11:36:57 -07001270 public PackedMap<Spec, Bounds> getGroupBounds() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001271 if (groupBounds == null) {
1272 groupBounds = createGroupBounds();
1273 }
1274 if (!groupBoundsValid) {
1275 computeGroupBounds();
1276 groupBoundsValid = true;
1277 }
1278 return groupBounds;
1279 }
1280
1281 // Add values computed by alignment - taking the max of all alignments in each span
Philip Milne48b55242011-06-29 11:09:45 -07001282 private PackedMap<Interval, MutableInt> createLinks(boolean min) {
1283 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class);
Philip Milne93cd6a62011-07-12 14:49:45 -07001284 Spec[] keys = getGroupBounds().keys;
Philip Milne48b55242011-06-29 11:09:45 -07001285 for (int i = 0, N = keys.length; i < N; i++) {
1286 Interval span = min ? keys[i].span : keys[i].span.inverse();
1287 result.put(span, new MutableInt());
Philip Milne3f8956d2011-05-13 17:29:00 +01001288 }
Philip Milne48b55242011-06-29 11:09:45 -07001289 return result.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001290 }
1291
Philip Milne48b55242011-06-29 11:09:45 -07001292 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) {
1293 MutableInt[] spans = links.values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001294 for (int i = 0; i < spans.length; i++) {
1295 spans[i].reset();
1296 }
1297
Philip Milne5d1a9842011-07-07 11:47:08 -07001298 // Use getter to trigger a re-evaluation
Philip Milne48b55242011-06-29 11:09:45 -07001299 Bounds[] bounds = getGroupBounds().values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001300 for (int i = 0; i < bounds.length; i++) {
Philip Milne48b55242011-06-29 11:09:45 -07001301 int size = bounds[i].size(min);
Philip Milne48b55242011-06-29 11:09:45 -07001302 MutableInt valueHolder = links.getValue(i);
Philip Milne5125e212011-07-21 11:39:37 -07001303 // this effectively takes the max() of the minima and the min() of the maxima
1304 valueHolder.value = max(valueHolder.value, min ? size : -size);
Philip Milne3f8956d2011-05-13 17:29:00 +01001305 }
1306 }
1307
Philip Milne48b55242011-06-29 11:09:45 -07001308 private PackedMap<Interval, MutableInt> getForwardLinks() {
1309 if (forwardLinks == null) {
1310 forwardLinks = createLinks(true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001311 }
Philip Milne48b55242011-06-29 11:09:45 -07001312 if (!forwardLinksValid) {
1313 computeLinks(forwardLinks, true);
1314 forwardLinksValid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01001315 }
Philip Milne48b55242011-06-29 11:09:45 -07001316 return forwardLinks;
Philip Milne3f8956d2011-05-13 17:29:00 +01001317 }
1318
Philip Milne48b55242011-06-29 11:09:45 -07001319 private PackedMap<Interval, MutableInt> getBackwardLinks() {
1320 if (backwardLinks == null) {
1321 backwardLinks = createLinks(false);
1322 }
1323 if (!backwardLinksValid) {
1324 computeLinks(backwardLinks, false);
1325 backwardLinksValid = true;
1326 }
1327 return backwardLinks;
1328 }
1329
1330 private void include(List<Arc> arcs, Interval key, MutableInt size,
Philip Milneedd69512012-03-14 17:21:33 -07001331 boolean ignoreIfAlreadyPresent) {
Philip Milne48b55242011-06-29 11:09:45 -07001332 /*
1333 Remove self referential links.
1334 These appear:
1335 . as parental constraints when GridLayout has no children
1336 . when components have been marked as GONE
1337 */
1338 if (key.size() == 0) {
1339 return;
1340 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001341 // this bit below should really be computed outside here -
Philip Milne48b55242011-06-29 11:09:45 -07001342 // its just to stop default (row/col > 0) constraints obliterating valid entries
1343 if (ignoreIfAlreadyPresent) {
1344 for (Arc arc : arcs) {
1345 Interval span = arc.span;
1346 if (span.equals(key)) {
1347 return;
1348 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001349 }
1350 }
1351 arcs.add(new Arc(key, size));
1352 }
1353
Philip Milne48b55242011-06-29 11:09:45 -07001354 private void include(List<Arc> arcs, Interval key, MutableInt size) {
1355 include(arcs, key, size, true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001356 }
1357
Philip Milneaa616f32011-05-27 18:38:01 -07001358 // Group arcs by their first vertex, returning an array of arrays.
Philip Milne3f8956d2011-05-13 17:29:00 +01001359 // This is linear in the number of arcs.
Philip Milnef6679c82011-09-18 11:36:57 -07001360 Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
Philip Milne48b55242011-06-29 11:09:45 -07001361 int N = getCount() + 1; // the number of vertices
Philip Milne3f8956d2011-05-13 17:29:00 +01001362 Arc[][] result = new Arc[N][];
1363 int[] sizes = new int[N];
1364 for (Arc arc : arcs) {
1365 sizes[arc.span.min]++;
Philip Milne5d1a9842011-07-07 11:47:08 -07001366 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001367 for (int i = 0; i < sizes.length; i++) {
1368 result[i] = new Arc[sizes[i]];
1369 }
1370 // reuse the sizes array to hold the current last elements as we insert each arc
1371 Arrays.fill(sizes, 0);
1372 for (Arc arc : arcs) {
1373 int i = arc.span.min;
1374 result[i][sizes[i]++] = arc;
1375 }
1376
1377 return result;
1378 }
1379
Philip Milne48b55242011-06-29 11:09:45 -07001380 private Arc[] topologicalSort(final Arc[] arcs) {
1381 return new Object() {
1382 Arc[] result = new Arc[arcs.length];
1383 int cursor = result.length - 1;
1384 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001385 int[] visited = new int[getCount() + 1];
1386
Philip Milne48b55242011-06-29 11:09:45 -07001387 void walk(int loc) {
1388 switch (visited[loc]) {
1389 case NEW: {
1390 visited[loc] = PENDING;
1391 for (Arc arc : arcsByVertex[loc]) {
1392 walk(arc.span.max);
1393 result[cursor--] = arc;
Philip Milne3f8956d2011-05-13 17:29:00 +01001394 }
Philip Milne48b55242011-06-29 11:09:45 -07001395 visited[loc] = COMPLETE;
1396 break;
Philip Milne3f8956d2011-05-13 17:29:00 +01001397 }
Philip Milne48b55242011-06-29 11:09:45 -07001398 case PENDING: {
Philip Milneb65408f2012-05-21 10:44:46 -07001399 // le singe est dans l'arbre
Philip Milne48b55242011-06-29 11:09:45 -07001400 assert false;
1401 break;
1402 }
1403 case COMPLETE: {
1404 break;
1405 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001406 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001407 }
Philip Milne48b55242011-06-29 11:09:45 -07001408
1409 Arc[] sort() {
1410 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) {
1411 walk(loc);
1412 }
1413 assert cursor == -1;
1414 return result;
1415 }
1416 }.sort();
1417 }
1418
1419 private Arc[] topologicalSort(List<Arc> arcs) {
1420 return topologicalSort(arcs.toArray(new Arc[arcs.size()]));
Philip Milne3f8956d2011-05-13 17:29:00 +01001421 }
1422
Philip Milne48b55242011-06-29 11:09:45 -07001423 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) {
1424 for (int i = 0; i < links.keys.length; i++) {
1425 Interval key = links.keys[i];
1426 include(result, key, links.values[i], false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001427 }
Philip Milne48b55242011-06-29 11:09:45 -07001428 }
1429
1430 private Arc[] createArcs() {
1431 List<Arc> mins = new ArrayList<Arc>();
1432 List<Arc> maxs = new ArrayList<Arc>();
1433
1434 // Add the minimum values from the components.
1435 addComponentSizes(mins, getForwardLinks());
1436 // Add the maximum values from the components.
1437 addComponentSizes(maxs, getBackwardLinks());
Philip Milne3f8956d2011-05-13 17:29:00 +01001438
Philip Milne48b55242011-06-29 11:09:45 -07001439 // Add ordering constraints to prevent row/col sizes from going negative
Philip Milnef6679c82011-09-18 11:36:57 -07001440 if (orderPreserved) {
Philip Milne48b55242011-06-29 11:09:45 -07001441 // Add a constraint for every row/col
Philip Milne3f8956d2011-05-13 17:29:00 +01001442 for (int i = 0; i < getCount(); i++) {
Philip Milne899d5922011-07-21 11:39:37 -07001443 include(mins, new Interval(i, i + 1), new MutableInt(0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001444 }
1445 }
Philip Milne48b55242011-06-29 11:09:45 -07001446
1447 // Add the container constraints. Use the version of include that allows
1448 // duplicate entries in case a child spans the entire grid.
1449 int N = getCount();
1450 include(mins, new Interval(0, N), parentMin, false);
1451 include(maxs, new Interval(N, 0), parentMax, false);
1452
1453 // Sort
1454 Arc[] sMins = topologicalSort(mins);
1455 Arc[] sMaxs = topologicalSort(maxs);
1456
1457 return append(sMins, sMaxs);
1458 }
1459
1460 private void computeArcs() {
1461 // getting the links validates the values that are shared by the arc list
1462 getForwardLinks();
1463 getBackwardLinks();
Philip Milne3f8956d2011-05-13 17:29:00 +01001464 }
1465
Philip Milneaa616f32011-05-27 18:38:01 -07001466 public Arc[] getArcs() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001467 if (arcs == null) {
Philip Milneaa616f32011-05-27 18:38:01 -07001468 arcs = createArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001469 }
1470 if (!arcsValid) {
Philip Milne48b55242011-06-29 11:09:45 -07001471 computeArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001472 arcsValid = true;
1473 }
1474 return arcs;
1475 }
1476
Philip Milneaa616f32011-05-27 18:38:01 -07001477 private boolean relax(int[] locations, Arc entry) {
Philip Milne48b55242011-06-29 11:09:45 -07001478 if (!entry.valid) {
1479 return false;
1480 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001481 Interval span = entry.span;
1482 int u = span.min;
1483 int v = span.max;
1484 int value = entry.value.value;
1485 int candidate = locations[u] + value;
Philip Milneaa616f32011-05-27 18:38:01 -07001486 if (candidate > locations[v]) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001487 locations[v] = candidate;
1488 return true;
1489 }
1490 return false;
1491 }
1492
Philip Milnef6679c82011-09-18 11:36:57 -07001493 private void init(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001494 Arrays.fill(locations, 0);
Philip Milnef6679c82011-09-18 11:36:57 -07001495 }
1496
1497 private String arcsToString(List<Arc> arcs) {
Philip Milne4a145d72011-09-29 14:14:25 -07001498 String var = horizontal ? "x" : "y";
Philip Milnef6679c82011-09-18 11:36:57 -07001499 StringBuilder result = new StringBuilder();
Philip Milne4a145d72011-09-29 14:14:25 -07001500 boolean first = true;
1501 for (Arc arc : arcs) {
1502 if (first) {
1503 first = false;
Philip Milnef6679c82011-09-18 11:36:57 -07001504 } else {
Philip Milne4a145d72011-09-29 14:14:25 -07001505 result = result.append(", ");
Philip Milnef6679c82011-09-18 11:36:57 -07001506 }
1507 int src = arc.span.min;
1508 int dst = arc.span.max;
1509 int value = arc.value.value;
1510 result.append((src < dst) ?
Philip Milneedd69512012-03-14 17:21:33 -07001511 var + dst + "-" + var + src + ">=" + value :
1512 var + src + "-" + var + dst + "<=" + -value);
Philip Milnef6679c82011-09-18 11:36:57 -07001513
1514 }
1515 return result.toString();
1516 }
1517
1518 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) {
1519 List<Arc> culprits = new ArrayList<Arc>();
1520 List<Arc> removed = new ArrayList<Arc>();
1521 for (int c = 0; c < arcs.length; c++) {
1522 Arc arc = arcs[c];
1523 if (culprits0[c]) {
1524 culprits.add(arc);
1525 }
1526 if (!arc.valid) {
1527 removed.add(arc);
1528 }
1529 }
1530 Log.d(TAG, axisName + " constraints: " + arcsToString(culprits) + " are inconsistent; "
1531 + "permanently removing: " + arcsToString(removed) + ". ");
1532 }
1533
Philip Milneaa616f32011-05-27 18:38:01 -07001534 /*
1535 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
1536
1537 GridLayout converts its requirements into a system of linear constraints of the
1538 form:
1539
1540 x[i] - x[j] < a[k]
1541
1542 Where the x[i] are variables and the a[k] are constants.
1543
1544 For example, if the variables were instead labeled x, y, z we might have:
1545
1546 x - y < 17
1547 y - z < 23
1548 z - x < 42
1549
1550 This is a special case of the Linear Programming problem that is, in turn,
1551 equivalent to the single-source shortest paths problem on a digraph, for
1552 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
Philip Milneaa616f32011-05-27 18:38:01 -07001553 */
Philip Milne48b55242011-06-29 11:09:45 -07001554 private void solve(Arc[] arcs, int[] locations) {
Philip Milnef6679c82011-09-18 11:36:57 -07001555 String axisName = horizontal ? "horizontal" : "vertical";
Philip Milne3f8956d2011-05-13 17:29:00 +01001556 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
Philip Milnef6679c82011-09-18 11:36:57 -07001557 boolean[] originalCulprits = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001558
Philip Milnef6679c82011-09-18 11:36:57 -07001559 for (int p = 0; p < arcs.length; p++) {
1560 init(locations);
1561
1562 // We take one extra pass over traditional Bellman-Ford (and omit their final step)
1563 for (int i = 0; i < N; i++) {
1564 boolean changed = false;
1565 for (int j = 0, length = arcs.length; j < length; j++) {
1566 changed |= relax(locations, arcs[j]);
Philip Milne3f8956d2011-05-13 17:29:00 +01001567 }
Philip Milnef6679c82011-09-18 11:36:57 -07001568 if (!changed) {
1569 if (originalCulprits != null) {
1570 logError(axisName, arcs, originalCulprits);
1571 }
Philip Milnef6679c82011-09-18 11:36:57 -07001572 return;
Philip Milne48b55242011-06-29 11:09:45 -07001573 }
Philip Milnef6679c82011-09-18 11:36:57 -07001574 }
1575
1576 boolean[] culprits = new boolean[arcs.length];
1577 for (int i = 0; i < N; i++) {
1578 for (int j = 0, length = arcs.length; j < length; j++) {
1579 culprits[j] |= relax(locations, arcs[j]);
1580 }
1581 }
1582
1583 if (p == 0) {
1584 originalCulprits = culprits;
1585 }
1586
1587 for (int i = 0; i < arcs.length; i++) {
1588 if (culprits[i]) {
1589 Arc arc = arcs[i];
1590 // Only remove max values, min values alone cannot be inconsistent
1591 if (arc.span.min < arc.span.max) {
1592 continue;
1593 }
1594 arc.valid = false;
1595 break;
1596 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001597 }
1598 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001599 }
1600
Philip Milneaa616f32011-05-27 18:38:01 -07001601 private void computeMargins(boolean leading) {
1602 int[] margins = leading ? leadingMargins : trailingMargins;
Philip Milneb3a8c542011-06-20 16:02:59 -07001603 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001604 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001605 if (c.getVisibility() == View.GONE) continue;
Philip Milne3f8956d2011-05-13 17:29:00 +01001606 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001607 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1608 Interval span = spec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001609 int index = leading ? span.min : span.max;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001610 margins[index] = max(margins[index], getMargin1(c, horizontal, leading));
Philip Milne3f8956d2011-05-13 17:29:00 +01001611 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001612 }
1613
Philip Milnef6679c82011-09-18 11:36:57 -07001614 // External entry points
1615
1616 public int[] getLeadingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001617 if (leadingMargins == null) {
1618 leadingMargins = new int[getCount() + 1];
1619 }
1620 if (!leadingMarginsValid) {
1621 computeMargins(true);
1622 leadingMarginsValid = true;
1623 }
1624 return leadingMargins;
1625 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001626
Philip Milnef6679c82011-09-18 11:36:57 -07001627 public int[] getTrailingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001628 if (trailingMargins == null) {
1629 trailingMargins = new int[getCount() + 1];
1630 }
1631 if (!trailingMarginsValid) {
1632 computeMargins(false);
1633 trailingMarginsValid = true;
1634 }
1635 return trailingMargins;
1636 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001637
Philip Milne48b55242011-06-29 11:09:45 -07001638 private void computeLocations(int[] a) {
Philip Milnef6679c82011-09-18 11:36:57 -07001639 solve(getArcs(), a);
Philip Milne4a145d72011-09-29 14:14:25 -07001640 if (!orderPreserved) {
1641 // Solve returns the smallest solution to the constraint system for which all
1642 // values are positive. One value is therefore zero - though if the row/col
1643 // order is not preserved this may not be the first vertex. For consistency,
1644 // translate all the values so that they measure the distance from a[0]; the
1645 // leading edge of the parent. After this transformation some values may be
1646 // negative.
1647 int a0 = a[0];
1648 for (int i = 0, N = a.length; i < N; i++) {
1649 a[i] = a[i] - a0;
1650 }
1651 }
Philip Milneaa616f32011-05-27 18:38:01 -07001652 }
1653
Philip Milnef6679c82011-09-18 11:36:57 -07001654 public int[] getLocations() {
Philip Milneaa616f32011-05-27 18:38:01 -07001655 if (locations == null) {
1656 int N = getCount() + 1;
1657 locations = new int[N];
1658 }
Philip Milne48b55242011-06-29 11:09:45 -07001659 if (!locationsValid) {
1660 computeLocations(locations);
1661 locationsValid = true;
1662 }
Philip Milneaa616f32011-05-27 18:38:01 -07001663 return locations;
1664 }
1665
Philip Milne3f8956d2011-05-13 17:29:00 +01001666 private int size(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001667 // The parental edges are attached to vertices 0 and N - even when order is not
1668 // being preserved and other vertices fall outside this range. Measure the distance
1669 // between vertices 0 and N, assuming that locations[0] = 0.
1670 return locations[getCount()];
Philip Milne3f8956d2011-05-13 17:29:00 +01001671 }
1672
Philip Milne48b55242011-06-29 11:09:45 -07001673 private void setParentConstraints(int min, int max) {
1674 parentMin.value = min;
1675 parentMax.value = -max;
1676 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001677 }
1678
Philip Milne48b55242011-06-29 11:09:45 -07001679 private int getMeasure(int min, int max) {
1680 setParentConstraints(min, max);
1681 return size(getLocations());
1682 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001683
Philip Milnef6679c82011-09-18 11:36:57 -07001684 public int getMeasure(int measureSpec) {
Philip Milne48b55242011-06-29 11:09:45 -07001685 int mode = MeasureSpec.getMode(measureSpec);
1686 int size = MeasureSpec.getSize(measureSpec);
1687 switch (mode) {
1688 case MeasureSpec.UNSPECIFIED: {
Philip Milne93cd6a62011-07-12 14:49:45 -07001689 return getMeasure(0, MAX_SIZE);
Philip Milne48b55242011-06-29 11:09:45 -07001690 }
1691 case MeasureSpec.EXACTLY: {
1692 return getMeasure(size, size);
1693 }
1694 case MeasureSpec.AT_MOST: {
1695 return getMeasure(0, size);
1696 }
1697 default: {
1698 assert false;
1699 return 0;
1700 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001701 }
Philip Milne48b55242011-06-29 11:09:45 -07001702 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001703
Philip Milnef6679c82011-09-18 11:36:57 -07001704 public void layout(int size) {
Philip Milne48b55242011-06-29 11:09:45 -07001705 setParentConstraints(size, size);
1706 getLocations();
Philip Milne3f8956d2011-05-13 17:29:00 +01001707 }
1708
Philip Milnef6679c82011-09-18 11:36:57 -07001709 public void invalidateStructure() {
Philip Milne4a145d72011-09-29 14:14:25 -07001710 maxIndex = UNDEFINED;
Philip Milneaa616f32011-05-27 18:38:01 -07001711
Philip Milne3f8956d2011-05-13 17:29:00 +01001712 groupBounds = null;
Philip Milne48b55242011-06-29 11:09:45 -07001713 forwardLinks = null;
1714 backwardLinks = null;
1715
Philip Milneaa616f32011-05-27 18:38:01 -07001716 leadingMargins = null;
1717 trailingMargins = null;
Philip Milnec9885f62011-06-15 17:07:35 -07001718 arcs = null;
Philip Milne48b55242011-06-29 11:09:45 -07001719
Philip Milneaa616f32011-05-27 18:38:01 -07001720 locations = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001721
1722 invalidateValues();
1723 }
1724
Philip Milnef6679c82011-09-18 11:36:57 -07001725 public void invalidateValues() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001726 groupBoundsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001727 forwardLinksValid = false;
1728 backwardLinksValid = false;
1729
Philip Milneaa616f32011-05-27 18:38:01 -07001730 leadingMarginsValid = false;
1731 trailingMarginsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001732 arcsValid = false;
1733
1734 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001735 }
1736 }
1737
1738 /**
1739 * Layout information associated with each of the children of a GridLayout.
1740 * <p>
1741 * GridLayout supports both row and column spanning and arbitrary forms of alignment within
1742 * each cell group. The fundamental parameters associated with each cell group are
1743 * gathered into their vertical and horizontal components and stored
Philip Milne93cd6a62011-07-12 14:49:45 -07001744 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
Philip Milne6216e872012-02-16 17:15:50 -08001745 * {@link GridLayout.Spec Specs} are immutable structures
Philip Milneb0ce49b2011-07-15 15:39:07 -07001746 * and may be shared between the layout parameters of different children.
Philip Milne3f8956d2011-05-13 17:29:00 +01001747 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001748 * The row and column specs contain the leading and trailing indices along each axis
Philip Milneaa616f32011-05-27 18:38:01 -07001749 * and together specify the four grid indices that delimit the cells of this cell group.
Philip Milne3f8956d2011-05-13 17:29:00 +01001750 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001751 * The alignment properties of the row and column specs together specify
Philip Milne3f8956d2011-05-13 17:29:00 +01001752 * both aspects of alignment within the cell group. It is also possible to specify a child's
1753 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
1754 * method.
Philip Milnef6679c82011-09-18 11:36:57 -07001755 *
1756 * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
1757 *
1758 * Because the default values of the {@link #width} and {@link #height}
1759 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
1760 * declared in the layout parameters of GridLayout's children. In addition,
1761 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
1762 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
1763 * instead controlled by the principle of <em>flexibility</em>,
1764 * as discussed in {@link GridLayout}.
1765 *
1766 * <h4>Summary</h4>
1767 *
1768 * You should not need to use either of the special size values:
1769 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
1770 * a GridLayout.
Philip Milne3f8956d2011-05-13 17:29:00 +01001771 *
1772 * <h4>Default values</h4>
1773 *
1774 * <ul>
1775 * <li>{@link #width} = {@link #WRAP_CONTENT}</li>
1776 * <li>{@link #height} = {@link #WRAP_CONTENT}</li>
1777 * <li>{@link #topMargin} = 0 when
1778 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001779 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001780 * indicate that a default value should be computed on demand. </li>
1781 * <li>{@link #leftMargin} = 0 when
1782 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001783 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001784 * indicate that a default value should be computed on demand. </li>
1785 * <li>{@link #bottomMargin} = 0 when
1786 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001787 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001788 * indicate that a default value should be computed on demand. </li>
1789 * <li>{@link #rightMargin} = 0 when
1790 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001791 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001792 * indicate that a default value should be computed on demand. </li>
Philip Milnef6679c82011-09-18 11:36:57 -07001793 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
1794 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
1795 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
1796 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
1797 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
Philip Milne6216e872012-02-16 17:15:50 -08001798 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01001799 * </ul>
1800 *
Philip Milnef6679c82011-09-18 11:36:57 -07001801 * See {@link GridLayout} for a more complete description of the conventions
1802 * used by GridLayout in the interpretation of the properties of this class.
1803 *
Philip Milne3f8956d2011-05-13 17:29:00 +01001804 * @attr ref android.R.styleable#GridLayout_Layout_layout_row
1805 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
Philip Milne3f8956d2011-05-13 17:29:00 +01001806 * @attr ref android.R.styleable#GridLayout_Layout_layout_column
1807 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
Philip Milne3f8956d2011-05-13 17:29:00 +01001808 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
1809 */
1810 public static class LayoutParams extends MarginLayoutParams {
1811
1812 // Default values
1813
1814 private static final int DEFAULT_WIDTH = WRAP_CONTENT;
1815 private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
1816 private static final int DEFAULT_MARGIN = UNDEFINED;
1817 private static final int DEFAULT_ROW = UNDEFINED;
1818 private static final int DEFAULT_COLUMN = UNDEFINED;
Philip Milnef4748702011-06-09 18:30:32 -07001819 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01001820 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
Philip Milne3f8956d2011-05-13 17:29:00 +01001821
1822 // TypedArray indices
1823
Philip Milneb0ce49b2011-07-15 15:39:07 -07001824 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin;
1825 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft;
1826 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop;
1827 private static final int RIGHT_MARGIN =
1828 R.styleable.ViewGroup_MarginLayout_layout_marginRight;
Philip Milne3f8956d2011-05-13 17:29:00 +01001829 private static final int BOTTOM_MARGIN =
Philip Milneb0ce49b2011-07-15 15:39:07 -07001830 R.styleable.ViewGroup_MarginLayout_layout_marginBottom;
Philip Milne3f8956d2011-05-13 17:29:00 +01001831
Philip Milneb0ce49b2011-07-15 15:39:07 -07001832 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
1833 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
Philip Milne5d1a9842011-07-07 11:47:08 -07001834
Philip Milneb0ce49b2011-07-15 15:39:07 -07001835 private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
1836 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
Philip Milne5d1a9842011-07-07 11:47:08 -07001837
Philip Milneb0ce49b2011-07-15 15:39:07 -07001838 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
Philip Milne3f8956d2011-05-13 17:29:00 +01001839
1840 // Instance variables
1841
1842 /**
Philip Milnef6679c82011-09-18 11:36:57 -07001843 * The spec that defines the vertical characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01001844 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08001845 * If an assignment is made to this field after a measurement or layout operation
1846 * has already taken place, a call to
1847 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
1848 * must be made to notify GridLayout of the change. GridLayout is normally able
1849 * to detect when code fails to observe this rule, issue a warning and take steps to
1850 * compensate for the omission. This facility is implemented on a best effort basis
1851 * and should not be relied upon in production code - so it is best to include the above
1852 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01001853 */
Philip Milnef6679c82011-09-18 11:36:57 -07001854 public Spec rowSpec = Spec.UNDEFINED;
1855
Philip Milne3f8956d2011-05-13 17:29:00 +01001856 /**
Philip Milnef6679c82011-09-18 11:36:57 -07001857 * The spec that defines the horizontal characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01001858 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08001859 * If an assignment is made to this field after a measurement or layout operation
1860 * has already taken place, a call to
1861 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
1862 * must be made to notify GridLayout of the change. GridLayout is normally able
1863 * to detect when code fails to observe this rule, issue a warning and take steps to
1864 * compensate for the omission. This facility is implemented on a best effort basis
1865 * and should not be relied upon in production code - so it is best to include the above
1866 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01001867 */
Philip Milnef6679c82011-09-18 11:36:57 -07001868 public Spec columnSpec = Spec.UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001869
1870 // Constructors
1871
1872 private LayoutParams(
1873 int width, int height,
1874 int left, int top, int right, int bottom,
Philip Milne93cd6a62011-07-12 14:49:45 -07001875 Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001876 super(width, height);
1877 setMargins(left, top, right, bottom);
Philip Milne93cd6a62011-07-12 14:49:45 -07001878 this.rowSpec = rowSpec;
1879 this.columnSpec = columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001880 }
1881
1882 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07001883 * Constructs a new LayoutParams instance for this <code>rowSpec</code>
1884 * and <code>columnSpec</code>. All other fields are initialized with
Philip Milne3f8956d2011-05-13 17:29:00 +01001885 * default values as defined in {@link LayoutParams}.
1886 *
Philip Milne93cd6a62011-07-12 14:49:45 -07001887 * @param rowSpec the rowSpec
1888 * @param columnSpec the columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +01001889 */
Philip Milne93cd6a62011-07-12 14:49:45 -07001890 public LayoutParams(Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001891 this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
1892 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
Philip Milne93cd6a62011-07-12 14:49:45 -07001893 rowSpec, columnSpec);
Philip Milne3f8956d2011-05-13 17:29:00 +01001894 }
1895
1896 /**
1897 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
1898 */
1899 public LayoutParams() {
Philip Milnef6679c82011-09-18 11:36:57 -07001900 this(Spec.UNDEFINED, Spec.UNDEFINED);
Philip Milne3f8956d2011-05-13 17:29:00 +01001901 }
1902
1903 // Copying constructors
1904
1905 /**
1906 * {@inheritDoc}
1907 */
1908 public LayoutParams(ViewGroup.LayoutParams params) {
1909 super(params);
1910 }
1911
1912 /**
1913 * {@inheritDoc}
1914 */
1915 public LayoutParams(MarginLayoutParams params) {
1916 super(params);
1917 }
1918
1919 /**
1920 * {@inheritDoc}
1921 */
1922 public LayoutParams(LayoutParams that) {
1923 super(that);
Philip Milnef6679c82011-09-18 11:36:57 -07001924 this.rowSpec = that.rowSpec;
1925 this.columnSpec = that.columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001926 }
1927
1928 // AttributeSet constructors
1929
Philip Milne3f8956d2011-05-13 17:29:00 +01001930 /**
1931 * {@inheritDoc}
1932 *
1933 * Values not defined in the attribute set take the default values
1934 * defined in {@link LayoutParams}.
1935 */
1936 public LayoutParams(Context context, AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -07001937 super(context, attrs);
1938 reInitSuper(context, attrs);
1939 init(context, attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001940 }
1941
1942 // Implementation
1943
Philip Milne3f8956d2011-05-13 17:29:00 +01001944 // Reinitialise the margins using a different default policy than MarginLayoutParams.
1945 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
1946 // so that a layout manager default can be accessed post set up. We need this as, at the
1947 // point of installation, we do not know how many rows/cols there are and therefore
1948 // which elements are positioned next to the container's trailing edges. We need to
1949 // know this as margins around the container's boundary should have different
1950 // defaults to those between peers.
1951
1952 // This method could be parametrized and moved into MarginLayout.
1953 private void reInitSuper(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07001954 TypedArray a =
1955 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
Philip Milne3f8956d2011-05-13 17:29:00 +01001956 try {
1957 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
1958
1959 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
1960 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
1961 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
1962 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
1963 } finally {
1964 a.recycle();
1965 }
1966 }
1967
Philip Milne5125e212011-07-21 11:39:37 -07001968 private void init(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07001969 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
Philip Milne3f8956d2011-05-13 17:29:00 +01001970 try {
Philip Milne5125e212011-07-21 11:39:37 -07001971 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
Philip Milne3f8956d2011-05-13 17:29:00 +01001972
Philip Milne1e548252011-06-16 19:02:33 -07001973 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
Philip Milne5125e212011-07-21 11:39:37 -07001974 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne899d5922011-07-21 11:39:37 -07001975 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01001976
Philip Milne1e548252011-06-16 19:02:33 -07001977 int row = a.getInt(ROW, DEFAULT_ROW);
1978 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne899d5922011-07-21 11:39:37 -07001979 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
Philip Milne3f8956d2011-05-13 17:29:00 +01001980 } finally {
1981 a.recycle();
1982 }
1983 }
1984
1985 /**
Philip Milne7fd94872011-06-07 20:14:17 -07001986 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
Philip Milne6216e872012-02-16 17:15:50 -08001987 * See {@link Gravity}.
Philip Milne3f8956d2011-05-13 17:29:00 +01001988 *
Philip Milne7fd94872011-06-07 20:14:17 -07001989 * @param gravity the new gravity value
Philip Milne3f8956d2011-05-13 17:29:00 +01001990 *
1991 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
1992 */
1993 public void setGravity(int gravity) {
Philip Milne5125e212011-07-21 11:39:37 -07001994 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
1995 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01001996 }
1997
1998 @Override
1999 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
2000 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
2001 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
2002 }
2003
Philip Milnef6679c82011-09-18 11:36:57 -07002004 final void setRowSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002005 rowSpec = rowSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002006 }
2007
Philip Milnef6679c82011-09-18 11:36:57 -07002008 final void setColumnSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002009 columnSpec = columnSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002010 }
Philip Milned7dd8902012-01-26 16:55:30 -08002011
2012 @Override
2013 public boolean equals(Object o) {
2014 if (this == o) return true;
2015 if (o == null || getClass() != o.getClass()) return false;
2016
2017 LayoutParams that = (LayoutParams) o;
2018
2019 if (!columnSpec.equals(that.columnSpec)) return false;
2020 if (!rowSpec.equals(that.rowSpec)) return false;
2021
2022 return true;
2023 }
2024
2025 @Override
2026 public int hashCode() {
2027 int result = rowSpec.hashCode();
2028 result = 31 * result + columnSpec.hashCode();
2029 return result;
2030 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002031 }
2032
Philip Milneaa616f32011-05-27 18:38:01 -07002033 /*
2034 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
2035 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
2036 */
Philip Milnef6679c82011-09-18 11:36:57 -07002037 final static class Arc {
Philip Milne3f8956d2011-05-13 17:29:00 +01002038 public final Interval span;
Philip Milneaa616f32011-05-27 18:38:01 -07002039 public final MutableInt value;
Philip Milne48b55242011-06-29 11:09:45 -07002040 public boolean valid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01002041
Philip Milneaa616f32011-05-27 18:38:01 -07002042 public Arc(Interval span, MutableInt value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002043 this.span = span;
2044 this.value = value;
2045 }
2046
2047 @Override
2048 public String toString() {
Philip Milne48b55242011-06-29 11:09:45 -07002049 return span + " " + (!valid ? "+>" : "->") + " " + value;
Philip Milne3f8956d2011-05-13 17:29:00 +01002050 }
2051 }
2052
2053 // A mutable Integer - used to avoid heap allocation during the layout operation
2054
Philip Milnef6679c82011-09-18 11:36:57 -07002055 final static class MutableInt {
Philip Milne3f8956d2011-05-13 17:29:00 +01002056 public int value;
2057
Philip Milnef6679c82011-09-18 11:36:57 -07002058 public MutableInt() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002059 reset();
2060 }
2061
Philip Milnef6679c82011-09-18 11:36:57 -07002062 public MutableInt(int value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002063 this.value = value;
2064 }
2065
Philip Milnef6679c82011-09-18 11:36:57 -07002066 public void reset() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002067 value = Integer.MIN_VALUE;
2068 }
Philip Milne48b55242011-06-29 11:09:45 -07002069
2070 @Override
2071 public String toString() {
2072 return Integer.toString(value);
2073 }
2074 }
2075
Philip Milnef6679c82011-09-18 11:36:57 -07002076 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
Philip Milne48b55242011-06-29 11:09:45 -07002077 private final Class<K> keyType;
2078 private final Class<V> valueType;
2079
2080 private Assoc(Class<K> keyType, Class<V> valueType) {
2081 this.keyType = keyType;
2082 this.valueType = valueType;
2083 }
2084
Philip Milnef6679c82011-09-18 11:36:57 -07002085 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
Philip Milne48b55242011-06-29 11:09:45 -07002086 return new Assoc<K, V>(keyType, valueType);
2087 }
2088
2089 public void put(K key, V value) {
2090 add(Pair.create(key, value));
2091 }
2092
2093 @SuppressWarnings(value = "unchecked")
2094 public PackedMap<K, V> pack() {
2095 int N = size();
2096 K[] keys = (K[]) Array.newInstance(keyType, N);
2097 V[] values = (V[]) Array.newInstance(valueType, N);
2098 for (int i = 0; i < N; i++) {
2099 keys[i] = get(i).first;
2100 values[i] = get(i).second;
2101 }
2102 return new PackedMap<K, V>(keys, values);
2103 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002104 }
2105
Philip Milneaa616f32011-05-27 18:38:01 -07002106 /*
2107 This data structure is used in place of a Map where we have an index that refers to the order
2108 in which each key/value pairs were added to the map. In this case we store keys and values
2109 in arrays of a length that is equal to the number of unique keys. We also maintain an
2110 array of indexes from insertion order to the compacted arrays of keys and values.
2111
2112 Note that behavior differs from that of a LinkedHashMap in that repeated entries
2113 *do* get added multiples times. So the length of index is equals to the number of
2114 items added.
2115
2116 This is useful in the GridLayout class where we can rely on the order of children not
2117 changing during layout - to use integer-based lookup for our internal structures
2118 rather than using (and storing) an implementation of Map<Key, ?>.
2119 */
Philip Milne3f8956d2011-05-13 17:29:00 +01002120 @SuppressWarnings(value = "unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -07002121 final static class PackedMap<K, V> {
Philip Milne3f8956d2011-05-13 17:29:00 +01002122 public final int[] index;
2123 public final K[] keys;
2124 public final V[] values;
2125
2126 private PackedMap(K[] keys, V[] values) {
2127 this.index = createIndex(keys);
2128
Philip Milneaa616f32011-05-27 18:38:01 -07002129 this.keys = compact(keys, index);
2130 this.values = compact(values, index);
Philip Milne3f8956d2011-05-13 17:29:00 +01002131 }
2132
Philip Milnef6679c82011-09-18 11:36:57 -07002133 public V getValue(int i) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002134 return values[index[i]];
2135 }
2136
2137 private static <K> int[] createIndex(K[] keys) {
2138 int size = keys.length;
2139 int[] result = new int[size];
2140
2141 Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
2142 for (int i = 0; i < size; i++) {
2143 K key = keys[i];
2144 Integer index = keyToIndex.get(key);
2145 if (index == null) {
2146 index = keyToIndex.size();
2147 keyToIndex.put(key, index);
2148 }
2149 result[i] = index;
2150 }
2151 return result;
2152 }
2153
Philip Milneaa616f32011-05-27 18:38:01 -07002154 /*
2155 Create a compact array of keys or values using the supplied index.
2156 */
2157 private static <K> K[] compact(K[] a, int[] index) {
2158 int size = a.length;
2159 Class<?> componentType = a.getClass().getComponentType();
Philip Milne51f17d52011-06-13 10:44:49 -07002160 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01002161
2162 // this overwrite duplicates, retaining the last equivalent entry
2163 for (int i = 0; i < size; i++) {
Philip Milneaa616f32011-05-27 18:38:01 -07002164 result[index[i]] = a[i];
Philip Milne3f8956d2011-05-13 17:29:00 +01002165 }
2166 return result;
2167 }
2168 }
2169
Philip Milneaa616f32011-05-27 18:38:01 -07002170 /*
Philip Milne93cd6a62011-07-12 14:49:45 -07002171 For each group (with a given alignment) we need to store the amount of space required
Philip Milne7fd94872011-06-07 20:14:17 -07002172 before the alignment point and the amount of space required after it. One side of this
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002173 calculation is always 0 for START and END alignments but we don't make use of this.
Philip Milneaa616f32011-05-27 18:38:01 -07002174 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
2175 simple optimisations are possible.
2176
2177 The general algorithm therefore is to create a Map (actually a PackedMap) from
Philip Milne93cd6a62011-07-12 14:49:45 -07002178 group to Bounds and to loop through all Views in the group taking the maximum
Philip Milneaa616f32011-05-27 18:38:01 -07002179 of the values for each View.
2180 */
Philip Milnef6679c82011-09-18 11:36:57 -07002181 static class Bounds {
Philip Milne7fd94872011-06-07 20:14:17 -07002182 public int before;
2183 public int after;
Philip Milne5125e212011-07-21 11:39:37 -07002184 public int flexibility; // we're flexible iff all included specs are flexible
Philip Milne3f8956d2011-05-13 17:29:00 +01002185
2186 private Bounds() {
2187 reset();
2188 }
2189
Philip Milnea1f7b102011-06-23 11:10:13 -07002190 protected void reset() {
Philip Milne7fd94872011-06-07 20:14:17 -07002191 before = Integer.MIN_VALUE;
2192 after = Integer.MIN_VALUE;
Philip Milne5125e212011-07-21 11:39:37 -07002193 flexibility = CAN_STRETCH; // from the above, we're flexible when empty
Philip Milne3f8956d2011-05-13 17:29:00 +01002194 }
2195
Philip Milnea1f7b102011-06-23 11:10:13 -07002196 protected void include(int before, int after) {
Philip Milne7fd94872011-06-07 20:14:17 -07002197 this.before = max(this.before, before);
2198 this.after = max(this.after, after);
Philip Milne3f8956d2011-05-13 17:29:00 +01002199 }
2200
Philip Milne48b55242011-06-29 11:09:45 -07002201 protected int size(boolean min) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002202 if (!min) {
Philip Milne5125e212011-07-21 11:39:37 -07002203 if (canStretch(flexibility)) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002204 return MAX_SIZE;
2205 }
Philip Milne48b55242011-06-29 11:09:45 -07002206 }
Philip Milne7fd94872011-06-07 20:14:17 -07002207 return before + after;
Philip Milne3f8956d2011-05-13 17:29:00 +01002208 }
2209
Philip Milne1557fd72012-04-04 23:41:34 -07002210 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
Philip Milne7a23b492012-04-24 22:12:36 -07002211 return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne1557fd72012-04-04 23:41:34 -07002212 }
2213
2214 protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
Philip Milne5125e212011-07-21 11:39:37 -07002215 this.flexibility &= spec.getFlexibility();
Philip Milne1557fd72012-04-04 23:41:34 -07002216 boolean horizontal = axis.horizontal;
2217 int size = gl.getMeasurementIncludingMargin(c, horizontal);
2218 Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002219 // todo test this works correctly when the returned value is UNDEFINED
Philip Milne7a23b492012-04-24 22:12:36 -07002220 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne48b55242011-06-29 11:09:45 -07002221 include(before, size - before);
Philip Milnea1f7b102011-06-23 11:10:13 -07002222 }
2223
Philip Milne3f8956d2011-05-13 17:29:00 +01002224 @Override
2225 public String toString() {
2226 return "Bounds{" +
Philip Milne7fd94872011-06-07 20:14:17 -07002227 "before=" + before +
2228 ", after=" + after +
Philip Milne3f8956d2011-05-13 17:29:00 +01002229 '}';
2230 }
2231 }
2232
2233 /**
2234 * An Interval represents a contiguous range of values that lie between
2235 * the interval's {@link #min} and {@link #max} values.
2236 * <p>
2237 * Intervals are immutable so may be passed as values and used as keys in hash tables.
2238 * It is not necessary to have multiple instances of Intervals which have the same
2239 * {@link #min} and {@link #max} values.
2240 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -07002241 * Intervals are often written as {@code [min, max]} and represent the set of values
2242 * {@code x} such that {@code min <= x < max}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002243 */
Philip Milnef6679c82011-09-18 11:36:57 -07002244 final static class Interval {
Philip Milne3f8956d2011-05-13 17:29:00 +01002245 /**
2246 * The minimum value.
2247 */
2248 public final int min;
Philip Milneaa616f32011-05-27 18:38:01 -07002249
Philip Milne3f8956d2011-05-13 17:29:00 +01002250 /**
2251 * The maximum value.
2252 */
2253 public final int max;
2254
2255 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002256 * Construct a new Interval, {@code interval}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002257 * <ul>
Philip Milne7fd94872011-06-07 20:14:17 -07002258 * <li> {@code interval.min = min} </li>
2259 * <li> {@code interval.max = max} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002260 * </ul>
2261 *
2262 * @param min the minimum value.
2263 * @param max the maximum value.
2264 */
2265 public Interval(int min, int max) {
2266 this.min = min;
2267 this.max = max;
2268 }
2269
Philip Milnef6679c82011-09-18 11:36:57 -07002270 int size() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002271 return max - min;
2272 }
2273
Philip Milnef6679c82011-09-18 11:36:57 -07002274 Interval inverse() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002275 return new Interval(max, min);
2276 }
2277
2278 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002279 * Returns {@code true} if the {@link #getClass class},
2280 * {@link #min} and {@link #max} properties of this Interval and the
2281 * supplied parameter are pairwise equal; {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002282 *
Philip Milne7fd94872011-06-07 20:14:17 -07002283 * @param that the object to compare this interval with
Philip Milne3f8956d2011-05-13 17:29:00 +01002284 *
2285 * @return {@code true} if the specified object is equal to this
Philip Milne7fd94872011-06-07 20:14:17 -07002286 * {@code Interval}, {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002287 */
2288 @Override
2289 public boolean equals(Object that) {
2290 if (this == that) {
2291 return true;
2292 }
2293 if (that == null || getClass() != that.getClass()) {
2294 return false;
2295 }
2296
2297 Interval interval = (Interval) that;
2298
2299 if (max != interval.max) {
2300 return false;
2301 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002302 //noinspection RedundantIfStatement
Philip Milne3f8956d2011-05-13 17:29:00 +01002303 if (min != interval.min) {
2304 return false;
2305 }
2306
2307 return true;
2308 }
2309
2310 @Override
2311 public int hashCode() {
2312 int result = min;
2313 result = 31 * result + max;
2314 return result;
2315 }
2316
2317 @Override
2318 public String toString() {
2319 return "[" + min + ", " + max + "]";
2320 }
2321 }
2322
Philip Milne899d5922011-07-21 11:39:37 -07002323 /**
2324 * A Spec defines the horizontal or vertical characteristics of a group of
Philip Milne4a145d72011-09-29 14:14:25 -07002325 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em>
2326 * along the appropriate axis.
Philip Milne899d5922011-07-21 11:39:37 -07002327 * <p>
2328 * The <em>grid indices</em> are the leading and trailing edges of this cell group.
2329 * See {@link GridLayout} for a description of the conventions used by GridLayout
2330 * for grid indices.
2331 * <p>
2332 * The <em>alignment</em> property specifies how cells should be aligned in this group.
2333 * For row groups, this specifies the vertical alignment.
2334 * For column groups, this specifies the horizontal alignment.
Philip Milne4a145d72011-09-29 14:14:25 -07002335 * <p>
2336 * Use the following static methods to create specs:
2337 * <ul>
2338 * <li>{@link #spec(int)}</li>
2339 * <li>{@link #spec(int, int)}</li>
2340 * <li>{@link #spec(int, Alignment)}</li>
2341 * <li>{@link #spec(int, int, Alignment)}</li>
2342 * </ul>
2343 *
Philip Milne899d5922011-07-21 11:39:37 -07002344 */
Philip Milne93cd6a62011-07-12 14:49:45 -07002345 public static class Spec {
Philip Milnef6679c82011-09-18 11:36:57 -07002346 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
2347
2348 final boolean startDefined;
Philip Milne48b55242011-06-29 11:09:45 -07002349 final Interval span;
Philip Milne93cd6a62011-07-12 14:49:45 -07002350 final Alignment alignment;
Philip Milne3f8956d2011-05-13 17:29:00 +01002351
Philip Milnef6679c82011-09-18 11:36:57 -07002352 private Spec(boolean startDefined, Interval span, Alignment alignment) {
2353 this.startDefined = startDefined;
Philip Milne5d1a9842011-07-07 11:47:08 -07002354 this.span = span;
2355 this.alignment = alignment;
Philip Milne5d1a9842011-07-07 11:47:08 -07002356 }
2357
Philip Milnef6679c82011-09-18 11:36:57 -07002358 private Spec(boolean startDefined, int start, int size, Alignment alignment) {
2359 this(startDefined, new Interval(start, start + size), alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002360 }
2361
Philip Milnef6679c82011-09-18 11:36:57 -07002362 final Spec copyWriteSpan(Interval span) {
2363 return new Spec(startDefined, span, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002364 }
2365
Philip Milnef6679c82011-09-18 11:36:57 -07002366 final Spec copyWriteAlignment(Alignment alignment) {
2367 return new Spec(startDefined, span, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002368 }
2369
Philip Milnef6679c82011-09-18 11:36:57 -07002370 final int getFlexibility() {
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002371 return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
Philip Milne5d1a9842011-07-07 11:47:08 -07002372 }
2373
Philip Milne3f8956d2011-05-13 17:29:00 +01002374 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002375 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
2376 * properties of this Spec and the supplied parameter are pairwise equal,
Philip Milne7fd94872011-06-07 20:14:17 -07002377 * {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002378 *
Philip Milne93cd6a62011-07-12 14:49:45 -07002379 * @param that the object to compare this spec with
Philip Milne3f8956d2011-05-13 17:29:00 +01002380 *
2381 * @return {@code true} if the specified object is equal to this
Philip Milne93cd6a62011-07-12 14:49:45 -07002382 * {@code Spec}; {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +01002383 */
2384 @Override
2385 public boolean equals(Object that) {
2386 if (this == that) {
2387 return true;
2388 }
2389 if (that == null || getClass() != that.getClass()) {
2390 return false;
2391 }
2392
Philip Milne93cd6a62011-07-12 14:49:45 -07002393 Spec spec = (Spec) that;
Philip Milne3f8956d2011-05-13 17:29:00 +01002394
Philip Milne93cd6a62011-07-12 14:49:45 -07002395 if (!alignment.equals(spec.alignment)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002396 return false;
2397 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002398 //noinspection RedundantIfStatement
Philip Milne93cd6a62011-07-12 14:49:45 -07002399 if (!span.equals(spec.span)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002400 return false;
2401 }
2402
2403 return true;
2404 }
2405
2406 @Override
2407 public int hashCode() {
2408 int result = span.hashCode();
2409 result = 31 * result + alignment.hashCode();
2410 return result;
2411 }
2412 }
2413
Philip Milne3f8956d2011-05-13 17:29:00 +01002414 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002415 * Return a Spec, {@code spec}, where:
2416 * <ul>
2417 * <li> {@code spec.span = [start, start + size]} </li>
2418 * <li> {@code spec.alignment = alignment} </li>
2419 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002420 * <p>
2421 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002422 *
2423 * @param start the start
2424 * @param size the size
2425 * @param alignment the alignment
2426 */
2427 public static Spec spec(int start, int size, Alignment alignment) {
Philip Milnef6679c82011-09-18 11:36:57 -07002428 return new Spec(start != UNDEFINED, start, size, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002429 }
2430
2431 /**
2432 * Return a Spec, {@code spec}, where:
2433 * <ul>
2434 * <li> {@code spec.span = [start, start + 1]} </li>
2435 * <li> {@code spec.alignment = alignment} </li>
2436 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002437 * <p>
2438 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002439 *
2440 * @param start the start index
2441 * @param alignment the alignment
Philip Milne7b757812012-09-19 18:13:44 -07002442 *
2443 * @see #spec(int, int, Alignment)
Philip Milne93cd6a62011-07-12 14:49:45 -07002444 */
2445 public static Spec spec(int start, Alignment alignment) {
2446 return spec(start, 1, alignment);
2447 }
2448
2449 /**
Philip Milne5125e212011-07-21 11:39:37 -07002450 * Return a Spec, {@code spec}, where:
2451 * <ul>
2452 * <li> {@code spec.span = [start, start + size]} </li>
2453 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002454 * <p>
2455 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002456 *
2457 * @param start the start
2458 * @param size the size
Philip Milne7b757812012-09-19 18:13:44 -07002459 *
2460 * @see #spec(int, Alignment)
Philip Milne5125e212011-07-21 11:39:37 -07002461 */
2462 public static Spec spec(int start, int size) {
2463 return spec(start, size, UNDEFINED_ALIGNMENT);
2464 }
2465
2466 /**
2467 * Return a Spec, {@code spec}, where:
2468 * <ul>
2469 * <li> {@code spec.span = [start, start + 1]} </li>
2470 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002471 * <p>
2472 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002473 *
2474 * @param start the start index
Philip Milne7b757812012-09-19 18:13:44 -07002475 *
2476 * @see #spec(int, int)
Philip Milne5125e212011-07-21 11:39:37 -07002477 */
2478 public static Spec spec(int start) {
2479 return spec(start, 1);
2480 }
2481
2482 /**
Philip Milne3f8956d2011-05-13 17:29:00 +01002483 * Alignments specify where a view should be placed within a cell group and
2484 * what size it should be.
2485 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07002486 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
2487 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
2488 * {@code alignment}. Overall placement of the view in the cell
Philip Milne3f8956d2011-05-13 17:29:00 +01002489 * group is specified by the two alignments which act along each axis independently.
2490 * <p>
Philip Milnea1f7b102011-06-23 11:10:13 -07002491 * The GridLayout class defines the most common alignments used in general layout:
Philip Milne6216e872012-02-16 17:15:50 -08002492 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
2493 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
Philip Milnea1f7b102011-06-23 11:10:13 -07002494 */
2495 /*
Philip Milnec9885f62011-06-15 17:07:35 -07002496 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
Philip Milne3f8956d2011-05-13 17:29:00 +01002497 * to return the appropriate value for the type of alignment being defined.
2498 * The enclosing algorithms position the children
Philip Milne1e548252011-06-16 19:02:33 -07002499 * so that the locations defined by the alignment values
Philip Milne3f8956d2011-05-13 17:29:00 +01002500 * are the same for all of the views in a group.
2501 * <p>
Philip Milne3f8956d2011-05-13 17:29:00 +01002502 */
Philip Milnec9885f62011-06-15 17:07:35 -07002503 public static abstract class Alignment {
Philip Milne48b55242011-06-29 11:09:45 -07002504 Alignment() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002505 }
2506
Philip Milne6216e872012-02-16 17:15:50 -08002507 abstract int getGravityOffset(View view, int cellDelta);
2508
Philip Milne3f8956d2011-05-13 17:29:00 +01002509 /**
2510 * Returns an alignment value. In the case of vertical alignments the value
2511 * returned should indicate the distance from the top of the view to the
2512 * alignment location.
2513 * For horizontal alignments measurement is made from the left edge of the component.
2514 *
Philip Milnec9885f62011-06-15 17:07:35 -07002515 * @param view the view to which this alignment should be applied
2516 * @param viewSize the measured size of the view
Philip Milne7a23b492012-04-24 22:12:36 -07002517 * @param mode the basis of alignment: CLIP or OPTICAL
Philip Milneb3a8c542011-06-20 16:02:59 -07002518 * @return the alignment value
Philip Milne3f8956d2011-05-13 17:29:00 +01002519 */
Philip Milne7a23b492012-04-24 22:12:36 -07002520 abstract int getAlignmentValue(View view, int viewSize, int mode);
Philip Milne3f8956d2011-05-13 17:29:00 +01002521
2522 /**
2523 * Returns the size of the view specified by this alignment.
2524 * In the case of vertical alignments this method should return a height; for
2525 * horizontal alignments this method should return the width.
Philip Milnec9885f62011-06-15 17:07:35 -07002526 * <p>
2527 * The default implementation returns {@code viewSize}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002528 *
Philip Milnec9885f62011-06-15 17:07:35 -07002529 * @param view the view to which this alignment should be applied
2530 * @param viewSize the measured size of the view
2531 * @param cellSize the size of the cell into which this view will be placed
Philip Milneb3a8c542011-06-20 16:02:59 -07002532 * @return the aligned size
Philip Milne3f8956d2011-05-13 17:29:00 +01002533 */
Philip Milne6216e872012-02-16 17:15:50 -08002534 int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002535 return viewSize;
2536 }
Philip Milnea1f7b102011-06-23 11:10:13 -07002537
Philip Milne48b55242011-06-29 11:09:45 -07002538 Bounds getBounds() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002539 return new Bounds();
2540 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002541 }
2542
Philip Milnef6679c82011-09-18 11:36:57 -07002543 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002544 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002545 int getGravityOffset(View view, int cellDelta) {
2546 return UNDEFINED;
2547 }
2548
2549 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002550 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne5125e212011-07-21 11:39:37 -07002551 return UNDEFINED;
2552 }
2553 };
2554
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002555 /**
2556 * Indicates that a view should be aligned with the <em>start</em>
2557 * edges of the other views in its cell group.
2558 */
Philip Milnec9885f62011-06-15 17:07:35 -07002559 private static final Alignment LEADING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002560 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002561 int getGravityOffset(View view, int cellDelta) {
2562 return 0;
2563 }
2564
2565 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002566 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002567 return 0;
2568 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002569 };
2570
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002571 /**
2572 * Indicates that a view should be aligned with the <em>end</em>
2573 * edges of the other views in its cell group.
2574 */
Philip Milnec9885f62011-06-15 17:07:35 -07002575 private static final Alignment TRAILING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002576 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002577 int getGravityOffset(View view, int cellDelta) {
2578 return cellDelta;
2579 }
2580
2581 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002582 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002583 return viewSize;
2584 }
2585 };
2586
2587 /**
2588 * Indicates that a view should be aligned with the <em>top</em>
2589 * edges of the other views in its cell group.
2590 */
2591 public static final Alignment TOP = LEADING;
2592
2593 /**
2594 * Indicates that a view should be aligned with the <em>bottom</em>
2595 * edges of the other views in its cell group.
2596 */
2597 public static final Alignment BOTTOM = TRAILING;
2598
2599 /**
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002600 * Indicates that a view should be aligned with the <em>start</em>
Philip Milne3f8956d2011-05-13 17:29:00 +01002601 * edges of the other views in its cell group.
2602 */
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002603 public static final Alignment START = LEADING;
2604
2605 /**
2606 * Indicates that a view should be aligned with the <em>end</em>
2607 * edges of the other views in its cell group.
2608 */
2609 public static final Alignment END = TRAILING;
2610
Philip Milne6216e872012-02-16 17:15:50 -08002611 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002612 return new Alignment() {
2613 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002614 int getGravityOffset(View view, int cellDelta) {
2615 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta);
2616 }
2617
2618 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002619 public int getAlignmentValue(View view, int viewSize, int mode) {
2620 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002621 }
2622 };
2623 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002624
2625 /**
2626 * Indicates that a view should be aligned with the <em>left</em>
2627 * edges of the other views in its cell group.
2628 */
Philip Milne6216e872012-02-16 17:15:50 -08002629 public static final Alignment LEFT = createSwitchingAlignment(START, END);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002630
2631 /**
2632 * Indicates that a view should be aligned with the <em>right</em>
2633 * edges of the other views in its cell group.
2634 */
Philip Milne6216e872012-02-16 17:15:50 -08002635 public static final Alignment RIGHT = createSwitchingAlignment(END, START);
Philip Milne3f8956d2011-05-13 17:29:00 +01002636
2637 /**
2638 * Indicates that a view should be <em>centered</em> with the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002639 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
2640 * LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002641 */
Philip Milnec9885f62011-06-15 17:07:35 -07002642 public static final Alignment CENTER = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002643 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002644 int getGravityOffset(View view, int cellDelta) {
2645 return cellDelta >> 1;
2646 }
2647
2648 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002649 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002650 return viewSize >> 1;
2651 }
2652 };
2653
2654 /**
2655 * Indicates that a view should be aligned with the <em>baselines</em>
2656 * of the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002657 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002658 *
2659 * @see View#getBaseline()
2660 */
Philip Milnec9885f62011-06-15 17:07:35 -07002661 public static final Alignment BASELINE = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002662 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002663 int getGravityOffset(View view, int cellDelta) {
2664 return 0; // baseline gravity is top
2665 }
2666
2667 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002668 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002669 int baseline = view.getBaseline();
Philip Milne7b757812012-09-19 18:13:44 -07002670 return baseline == -1 ? UNDEFINED : baseline;
Philip Milnea1f7b102011-06-23 11:10:13 -07002671 }
2672
2673 @Override
2674 public Bounds getBounds() {
2675 return new Bounds() {
2676 /*
2677 In a baseline aligned row in which some components define a baseline
2678 and some don't, we need a third variable to properly account for all
2679 the sizes. This tracks the maximum size of all the components -
2680 including those that don't define a baseline.
2681 */
2682 private int size;
2683
2684 @Override
2685 protected void reset() {
2686 super.reset();
Philip Milne48b55242011-06-29 11:09:45 -07002687 size = Integer.MIN_VALUE;
Philip Milnea1f7b102011-06-23 11:10:13 -07002688 }
2689
2690 @Override
2691 protected void include(int before, int after) {
2692 super.include(before, after);
2693 size = max(size, before + after);
2694 }
2695
2696 @Override
Philip Milne48b55242011-06-29 11:09:45 -07002697 protected int size(boolean min) {
2698 return max(super.size(min), size);
Philip Milnea1f7b102011-06-23 11:10:13 -07002699 }
2700
2701 @Override
Philip Milne1557fd72012-04-04 23:41:34 -07002702 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
2703 return max(0, super.getOffset(gl, c, a, size, hrz));
Philip Milnea1f7b102011-06-23 11:10:13 -07002704 }
2705 };
Philip Milne3f8956d2011-05-13 17:29:00 +01002706 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002707 };
2708
2709 /**
2710 * Indicates that a view should expanded to fit the boundaries of its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002711 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
2712 * {@link LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002713 */
2714 public static final Alignment FILL = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002715 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002716 int getGravityOffset(View view, int cellDelta) {
2717 return 0;
2718 }
2719
2720 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002721 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002722 return UNDEFINED;
2723 }
2724
Philip Milnec9885f62011-06-15 17:07:35 -07002725 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002726 public int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002727 return cellSize;
2728 }
2729 };
Philip Milne48b55242011-06-29 11:09:45 -07002730
Philip Milnef6679c82011-09-18 11:36:57 -07002731 static boolean canStretch(int flexibility) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002732 return (flexibility & CAN_STRETCH) != 0;
2733 }
2734
Philip Milne5125e212011-07-21 11:39:37 -07002735 private static final int INFLEXIBLE = 0;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002736 private static final int CAN_STRETCH = 2;
Jim Miller452eec32011-06-16 18:32:44 -07002737}