blob: bda82a3842543c0204d8532c36a34e767c7434b1 [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;
23import android.graphics.Paint;
24import android.graphics.Rect;
25import android.util.AttributeSet;
26import android.util.Log;
27import android.view.Gravity;
28import android.view.View;
29import android.view.ViewGroup;
30import com.android.internal.R.styleable;
31
32import java.lang.reflect.Array;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.Collection;
36import java.util.Collections;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40
Philip Milneaa616f32011-05-27 18:38:01 -070041import static android.view.View.MeasureSpec.EXACTLY;
42import static android.view.View.MeasureSpec.UNSPECIFIED;
Philip Milne3f8956d2011-05-13 17:29:00 +010043import static java.lang.Math.max;
44import static java.lang.Math.min;
45
46/**
47 * A layout that places its children in a rectangular <em>grid</em>.
48 * <p>
49 * The grid is composed of a set of infinitely thin lines that separate the
50 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
Philip Milne7fd94872011-06-07 20:14:17 -070051 * by grid <em>indices</em>. A grid with {@code N} columns
52 * has {@code N + 1} grid indices that run from {@code 0}
53 * through {@code N} inclusive. Regardless of how GridLayout is
54 * configured, grid index {@code 0} is fixed to the leading edge of the
55 * container and grid index {@code N} is fixed to its trailing edge
Philip Milne3f8956d2011-05-13 17:29:00 +010056 * (after padding is taken into account).
57 *
58 * <h4>Row and Column Groups</h4>
59 *
60 * Children occupy one or more contiguous cells, as defined
61 * by their {@link GridLayout.LayoutParams#rowGroup rowGroup} and
62 * {@link GridLayout.LayoutParams#columnGroup columnGroup} layout parameters.
63 * Each group specifies the set of rows or columns that are to be
64 * occupied; and how children should be aligned within the resulting group of cells.
65 * Although cells do not normally overlap in a GridLayout, GridLayout does
66 * not prevent children being defined to occupy the same cell or group of cells.
67 * In this case however, there is no guarantee that children will not themselves
68 * overlap after the layout operation completes.
69 *
70 * <h4>Default Cell Assignment</h4>
71 *
72 * If no child specifies the row and column indices of the cell it
73 * wishes to occupy, GridLayout assigns cell locations automatically using its:
74 * {@link GridLayout#setOrientation(int) orientation},
75 * {@link GridLayout#setRowCount(int) rowCount} and
76 * {@link GridLayout#setColumnCount(int) columnCount} properties.
77 *
78 * <h4>Space</h4>
79 *
80 * Space between children may be specified either by using instances of the
81 * dedicated {@link Space} view or by setting the
82 *
83 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
84 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
85 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
86 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
87 *
88 * layout parameters. When the
89 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
90 * property is set, default margins around children are automatically
91 * allocated based on the child's visual characteristics. Each of the
92 * margins so defined may be independently overridden by an assignment
93 * to the appropriate layout parameter.
94 *
95 * <h4>Excess Space Distribution</h4>
96 *
97 * Like {@link LinearLayout}, a child's ability to stretch is controlled
98 * using <em>weights</em>, which are specified using the
99 * {@link GridLayout.LayoutParams#rowWeight rowWeight} and
100 * {@link GridLayout.LayoutParams#columnWeight columnWeight} layout parameters.
101 * <p>
102 * <p>
103 * See {@link GridLayout.LayoutParams} for a full description of the
104 * layout parameters used by GridLayout.
105 *
106 * @attr ref android.R.styleable#GridLayout_orientation
107 * @attr ref android.R.styleable#GridLayout_rowCount
108 * @attr ref android.R.styleable#GridLayout_columnCount
109 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
110 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
111 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
112 */
113public class GridLayout extends ViewGroup {
114
115 // Public constants
116
117 /**
118 * The horizontal orientation.
119 */
120 public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
Philip Milneaa616f32011-05-27 18:38:01 -0700121
Philip Milne3f8956d2011-05-13 17:29:00 +0100122 /**
123 * The vertical orientation.
124 */
125 public static final int VERTICAL = LinearLayout.VERTICAL;
126
Philip Milneaa616f32011-05-27 18:38:01 -0700127 /**
128 * The constant used to indicate that a value is undefined.
129 * Fields can use this value to indicate that their values
130 * have not yet been set. Similarly, methods can return this value
131 * to indicate that there is no suitable value that the implementation
132 * can return.
133 * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
134 * intended to avoid confusion between valid values whose sign may not be known.
135 */
136 public static final int UNDEFINED = Integer.MIN_VALUE;
137
Philip Milne3f8956d2011-05-13 17:29:00 +0100138 // Misc constants
139
140 private static final String TAG = GridLayout.class.getName();
141 private static final boolean DEBUG = false;
Philip Milne3f8956d2011-05-13 17:29:00 +0100142 private static final Paint GRID_PAINT = new Paint();
143 private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
144 private static final int MIN = 0;
145 private static final int PRF = 1;
146 private static final int MAX = 2;
147
148 // Defaults
149
150 private static final int DEFAULT_ORIENTATION = HORIZONTAL;
151 private static final int DEFAULT_COUNT = UNDEFINED;
152 private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
153 private static final boolean DEFAULT_ORDER_PRESERVED = false;
Philip Milneaa616f32011-05-27 18:38:01 -0700154 private static final boolean DEFAULT_MARGINS_INCLUDED = true;
155 // todo remove this
156 private static final int DEFAULT_CONTAINER_MARGIN = 20;
Philip Milne3f8956d2011-05-13 17:29:00 +0100157
158 // TypedArray indices
159
160 private static final int ORIENTATION = styleable.GridLayout_orientation;
161 private static final int ROW_COUNT = styleable.GridLayout_rowCount;
162 private static final int COLUMN_COUNT = styleable.GridLayout_columnCount;
163 private static final int USE_DEFAULT_MARGINS = styleable.GridLayout_useDefaultMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700164 private static final int MARGINS_INCLUDED = styleable.GridLayout_marginsIncludedInAlignment;
Philip Milne3f8956d2011-05-13 17:29:00 +0100165 private static final int ROW_ORDER_PRESERVED = styleable.GridLayout_rowOrderPreserved;
166 private static final int COLUMN_ORDER_PRESERVED = styleable.GridLayout_columnOrderPreserved;
167
Philip Milneaa616f32011-05-27 18:38:01 -0700168 // Static initialization
169
170 static {
171 GRID_PAINT.setColor(Color.argb(50, 255, 255, 255));
172 }
173
Philip Milne3f8956d2011-05-13 17:29:00 +0100174 // Instance variables
175
176 private final Axis mHorizontalAxis = new Axis(true);
177 private final Axis mVerticalAxis = new Axis(false);
178 private boolean mLayoutParamsValid = false;
179 private int mOrientation = DEFAULT_ORIENTATION;
180 private boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
Philip Milneaa616f32011-05-27 18:38:01 -0700181 private boolean mMarginsIncludedInAlignment = DEFAULT_MARGINS_INCLUDED;
Philip Milne3f8956d2011-05-13 17:29:00 +0100182 private int mDefaultGravity = Gravity.NO_GRAVITY;
Philip Milneaa616f32011-05-27 18:38:01 -0700183
184 /* package */ boolean accommodateBothMinAndMax = false;
Philip Milne3f8956d2011-05-13 17:29:00 +0100185
186 // Constructors
187
188 /**
189 * {@inheritDoc}
190 */
191 public GridLayout(Context context) {
192 super(context);
193 if (DEBUG) {
194 setWillNotDraw(false);
195 }
196 }
197
198 /**
199 * {@inheritDoc}
200 */
201 public GridLayout(Context context, AttributeSet attrs, int defStyle) {
202 super(context, attrs, defStyle);
203 processAttributes(context, attrs);
204 }
205
206 /**
207 * {@inheritDoc}
208 */
209 public GridLayout(Context context, AttributeSet attrs) {
210 super(context, attrs);
211 processAttributes(context, attrs);
212 }
213
214 private void processAttributes(Context context, AttributeSet attrs) {
215 TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout);
216 try {
217 setRowCount(a.getInteger(ROW_COUNT, DEFAULT_COUNT));
218 setColumnCount(a.getInteger(COLUMN_COUNT, DEFAULT_COUNT));
219 mOrientation = a.getInteger(ORIENTATION, DEFAULT_ORIENTATION);
220 mUseDefaultMargins = a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS);
Philip Milneaa616f32011-05-27 18:38:01 -0700221 mMarginsIncludedInAlignment = a.getBoolean(MARGINS_INCLUDED, DEFAULT_MARGINS_INCLUDED);
Philip Milne3f8956d2011-05-13 17:29:00 +0100222 setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
223 setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
224 } finally {
225 a.recycle();
226 }
227 }
228
229 // Implementation
230
231 /**
232 * Returns the current orientation.
233 *
Philip Milne7fd94872011-06-07 20:14:17 -0700234 * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100235 *
236 * @see #setOrientation(int)
237 *
238 * @attr ref android.R.styleable#GridLayout_orientation
239 */
240 public int getOrientation() {
241 return mOrientation;
242 }
243
244 /**
245 * The orientation property does not affect layout. Orientation is used
246 * only to generate default row/column indices when they are not specified
247 * by a component's layout parameters.
Philip Milne7fd94872011-06-07 20:14:17 -0700248 * <p>
249 * The default value of this property is {@link #HORIZONTAL}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100250 *
Philip Milne7fd94872011-06-07 20:14:17 -0700251 * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100252 *
253 * @see #getOrientation()
254 *
255 * @attr ref android.R.styleable#GridLayout_orientation
256 */
257 public void setOrientation(int orientation) {
258 if (mOrientation != orientation) {
259 mOrientation = orientation;
260 requestLayout();
261 }
262 }
263
264 /**
265 * Returns the current number of rows. This is either the last value that was set
266 * with {@link #setRowCount(int)} or, if no such value was set, the maximum
267 * value of each the upper bounds defined in {@link LayoutParams#rowGroup}.
268 *
269 * @return the current number of rows
270 *
271 * @see #setRowCount(int)
272 * @see LayoutParams#rowGroup
273 *
274 * @attr ref android.R.styleable#GridLayout_rowCount
275 */
276 public int getRowCount() {
277 return mVerticalAxis.getCount();
278 }
279
280 /**
281 * The rowCount property does not affect layout. RowCount is used
282 * only to generate default row/column indices when they are not specified
283 * by a component's layout parameters.
284 *
Philip Milne7fd94872011-06-07 20:14:17 -0700285 * @param rowCount the number of rows
Philip Milne3f8956d2011-05-13 17:29:00 +0100286 *
287 * @see #getRowCount()
288 * @see LayoutParams#rowGroup
289 *
290 * @attr ref android.R.styleable#GridLayout_rowCount
291 */
292 public void setRowCount(int rowCount) {
293 mVerticalAxis.setCount(rowCount);
294 }
295
296 /**
297 * Returns the current number of columns. This is either the last value that was set
298 * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
299 * value of each the upper bounds defined in {@link LayoutParams#columnGroup}.
300 *
301 * @return the current number of columns
302 *
303 * @see #setColumnCount(int)
304 * @see LayoutParams#columnGroup
305 *
306 * @attr ref android.R.styleable#GridLayout_columnCount
307 */
308 public int getColumnCount() {
309 return mHorizontalAxis.getCount();
310 }
311
312 /**
313 * The columnCount property does not affect layout. ColumnCount is used
314 * only to generate default column/column indices when they are not specified
315 * by a component's layout parameters.
316 *
317 * @param columnCount the number of columns.
318 *
319 * @see #getColumnCount()
320 * @see LayoutParams#columnGroup
321 *
322 * @attr ref android.R.styleable#GridLayout_columnCount
323 */
324 public void setColumnCount(int columnCount) {
325 mHorizontalAxis.setCount(columnCount);
326 }
327
328 /**
329 * Returns whether or not this GridLayout will allocate default margins when no
330 * corresponding layout parameters are defined.
331 *
Philip Milne7fd94872011-06-07 20:14:17 -0700332 * @return {@code true} if default margins should be allocated
Philip Milne3f8956d2011-05-13 17:29:00 +0100333 *
334 * @see #setUseDefaultMargins(boolean)
335 *
336 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
337 */
338 public boolean getUseDefaultMargins() {
339 return mUseDefaultMargins;
340 }
341
342 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700343 * When {@code true}, GridLayout allocates default margins around children
Philip Milne3f8956d2011-05-13 17:29:00 +0100344 * based on the child's visual characteristics. Each of the
345 * margins so defined may be independently overridden by an assignment
346 * to the appropriate layout parameter.
347 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700348 * When {@code false}, the default value of all margins is zero.
Philip Milneaa616f32011-05-27 18:38:01 -0700349 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700350 * When setting to {@code true}, consider setting the value of the
Philip Milneaa616f32011-05-27 18:38:01 -0700351 * {@link #setMarginsIncludedInAlignment(boolean) marginsIncludedInAlignment}
Philip Milne7fd94872011-06-07 20:14:17 -0700352 * property to {@code false}.
353 * <p>
354 * The default value of this property is {@code false}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100355 *
Philip Milne7fd94872011-06-07 20:14:17 -0700356 * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
Philip Milne3f8956d2011-05-13 17:29:00 +0100357 *
358 * @see #getUseDefaultMargins()
Philip Milneaa616f32011-05-27 18:38:01 -0700359 * @see #setMarginsIncludedInAlignment(boolean)
Philip Milne3f8956d2011-05-13 17:29:00 +0100360 *
361 * @see MarginLayoutParams#leftMargin
362 * @see MarginLayoutParams#topMargin
363 * @see MarginLayoutParams#rightMargin
364 * @see MarginLayoutParams#bottomMargin
365 *
366 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
367 */
368 public void setUseDefaultMargins(boolean useDefaultMargins) {
369 mUseDefaultMargins = useDefaultMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700370 requestLayout();
371 }
372
373 /**
374 * Returns whether GridLayout aligns the edges of the view or the edges
375 * of the larger rectangle created by extending the view by its associated
376 * margins.
377 *
378 * @see #setMarginsIncludedInAlignment(boolean)
379 *
Philip Milne7fd94872011-06-07 20:14:17 -0700380 * @return {@code true} if alignment is between edges including margins
Philip Milneaa616f32011-05-27 18:38:01 -0700381 *
382 * @attr ref android.R.styleable#GridLayout_marginsIncludedInAlignment
383 */
384 public boolean getMarginsIncludedInAlignment() {
385 return mMarginsIncludedInAlignment;
386 }
387
388 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700389 * When {@code true}, the bounds of a view are extended outwards according to its
Philip Milneaa616f32011-05-27 18:38:01 -0700390 * margins before the edges of the resulting rectangle are aligned.
Philip Milne7fd94872011-06-07 20:14:17 -0700391 * When {@code false}, alignment occurs between the bounds of the view - i.e.
Philip Milneaa616f32011-05-27 18:38:01 -0700392 * {@link #LEFT} alignment means align the left edges of the view.
Philip Milne7fd94872011-06-07 20:14:17 -0700393 * <p>
394 * The default value of this property is {@code true}.
Philip Milneaa616f32011-05-27 18:38:01 -0700395 *
Philip Milne7fd94872011-06-07 20:14:17 -0700396 * @param marginsIncludedInAlignment {@code true} if alignment between edges includes margins
Philip Milneaa616f32011-05-27 18:38:01 -0700397 *
398 * @see #getMarginsIncludedInAlignment()
399 *
400 * @attr ref android.R.styleable#GridLayout_marginsIncludedInAlignment
401 */
402 public void setMarginsIncludedInAlignment(boolean marginsIncludedInAlignment) {
403 mMarginsIncludedInAlignment = marginsIncludedInAlignment;
404 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100405 }
406
407 /**
408 * Returns whether or not row boundaries are ordered by their grid indices.
409 *
Philip Milne7fd94872011-06-07 20:14:17 -0700410 * @return {@code true} if row boundaries must appear in the order of their indices,
411 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100412 *
413 * @see #setRowOrderPreserved(boolean)
414 *
415 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
416 */
417 public boolean isRowOrderPreserved() {
418 return mVerticalAxis.isOrderPreserved();
419 }
420
421 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700422 * When this property is {@code false}, the default state, GridLayout
Philip Milne3f8956d2011-05-13 17:29:00 +0100423 * is at liberty to choose an order that better suits the heights of its children.
Philip Milneaa616f32011-05-27 18:38:01 -0700424 <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700425 * When this property is {@code true}, GridLayout is forced to place the row boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700426 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100427 * <p>
428 * GridLayout implements this specification by creating ordering constraints between
429 * the variables that represent the locations of the row boundaries.
430 *
Philip Milne7fd94872011-06-07 20:14:17 -0700431 * When this property is {@code true}, constraints are added for each pair of consecutive
432 * indices: i.e. between row boundaries: {@code [0..1], [1..2], [2..3],...} etc.
Philip Milne3f8956d2011-05-13 17:29:00 +0100433 *
Philip Milne7fd94872011-06-07 20:14:17 -0700434 * When the property is {@code false}, the ordering constraints are placed
Philip Milne3f8956d2011-05-13 17:29:00 +0100435 * only between boundaries that separate opposing edges of the layout's children.
Philip Milne7fd94872011-06-07 20:14:17 -0700436 * <p>
437 * The default value of this property is {@code false}.
438
439 * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
440 * of row boundaries
Philip Milne3f8956d2011-05-13 17:29:00 +0100441 *
442 * @see #isRowOrderPreserved()
443 *
444 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
445 */
446 public void setRowOrderPreserved(boolean rowOrderPreserved) {
447 mVerticalAxis.setOrderPreserved(rowOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700448 invalidateStructure();
449 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100450 }
451
452 /**
453 * Returns whether or not column boundaries are ordered by their grid indices.
454 *
Philip Milne7fd94872011-06-07 20:14:17 -0700455 * @return {@code true} if column boundaries must appear in the order of their indices,
456 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100457 *
458 * @see #setColumnOrderPreserved(boolean)
459 *
460 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
461 */
462 public boolean isColumnOrderPreserved() {
463 return mHorizontalAxis.isOrderPreserved();
464 }
465
466 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700467 * When this property is {@code false}, the default state, GridLayout
Philip Milne3f8956d2011-05-13 17:29:00 +0100468 * is at liberty to choose an order that better suits the widths of its children.
Philip Milneaa616f32011-05-27 18:38:01 -0700469 <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700470 * When this property is {@code true}, GridLayout is forced to place the column boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700471 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100472 * <p>
473 * GridLayout implements this specification by creating ordering constraints between
474 * the variables that represent the locations of the column boundaries.
475 *
Philip Milne7fd94872011-06-07 20:14:17 -0700476 * When this property is {@code true}, constraints are added for each pair of consecutive
477 * indices: i.e. between column boundaries: {@code [0..1], [1..2], [2..3],...} etc.
Philip Milne3f8956d2011-05-13 17:29:00 +0100478 *
Philip Milne7fd94872011-06-07 20:14:17 -0700479 * When the property is {@code false}, the ordering constraints are placed
Philip Milne3f8956d2011-05-13 17:29:00 +0100480 * only between boundaries that separate opposing edges of the layout's children.
Philip Milne7fd94872011-06-07 20:14:17 -0700481 * <p>
482 * The default value of this property is {@code false}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100483 *
Philip Milne7fd94872011-06-07 20:14:17 -0700484 * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
Philip Milne3f8956d2011-05-13 17:29:00 +0100485 * of column boundaries.
486 *
487 * @see #isColumnOrderPreserved()
488 *
489 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
490 */
491 public void setColumnOrderPreserved(boolean columnOrderPreserved) {
492 mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700493 invalidateStructure();
494 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100495 }
496
Philip Milneaa616f32011-05-27 18:38:01 -0700497 private static int sum(float[] a) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100498 int result = 0;
499 for (int i = 0, length = a.length; i < length; i++) {
500 result += a[i];
501 }
502 return result;
503 }
504
505 private int getDefaultMargin(View c, boolean leading, boolean horizontal) {
506 // In the absence of any other information, calculate a default gap such
507 // that, in a grid of identical components, the heights and the vertical
508 // gaps are in the proportion of the golden ratio.
509 // To effect this with equal margins at each edge, set each of the
510 // four margin values to half this amount.
Philip Milne3f8956d2011-05-13 17:29:00 +0100511 return (int) (c.getMeasuredHeight() / GOLDEN_RATIO / 2);
512 }
513
514 private int getDefaultMargin(View c, boolean isAtEdge, boolean leading, boolean horizontal) {
Philip Milneaa616f32011-05-27 18:38:01 -0700515 // todo remove DEFAULT_CONTAINER_MARGIN. Use padding? Seek advice on Themes/Styles, etc.
516 return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, leading, horizontal);
Philip Milne3f8956d2011-05-13 17:29:00 +0100517 }
518
519 private int getDefaultMarginValue(View c, LayoutParams p, boolean leading, boolean horizontal) {
520 if (!mUseDefaultMargins) {
521 return 0;
522 }
523 Group group = horizontal ? p.columnGroup : p.rowGroup;
524 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
525 Interval span = group.span;
Philip Milneaa616f32011-05-27 18:38:01 -0700526 boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount());
Philip Milne3f8956d2011-05-13 17:29:00 +0100527
528 return getDefaultMargin(c, isAtEdge, leading, horizontal);
529 }
530
531 private int getMargin(View view, boolean leading, boolean horizontal) {
532 LayoutParams lp = getLayoutParams(view);
533 int margin = horizontal ?
Philip Milneaa616f32011-05-27 18:38:01 -0700534 (leading ? lp.leftMargin : lp.rightMargin) :
535 (leading ? lp.topMargin : lp.bottomMargin);
Philip Milne3f8956d2011-05-13 17:29:00 +0100536 return margin == UNDEFINED ? getDefaultMarginValue(view, lp, leading, horizontal) : margin;
537 }
538
Philip Milnef4748702011-06-09 18:30:32 -0700539 private static int valueIfDefined(int value, int defaultValue) {
540 return (value != UNDEFINED) ? value : defaultValue;
Philip Milne3f8956d2011-05-13 17:29:00 +0100541 }
542
Philip Milnef4748702011-06-09 18:30:32 -0700543 // install default indices for cells that don't define them
Philip Milne3f8956d2011-05-13 17:29:00 +0100544 private void validateLayoutParams() {
Philip Milnef4748702011-06-09 18:30:32 -0700545 new Object() {
546 public int maxSize = 0;
Philip Milne3f8956d2011-05-13 17:29:00 +0100547
Philip Milnef4748702011-06-09 18:30:32 -0700548 private int valueIfDefined2(int value, int defaultValue) {
549 if (value != UNDEFINED) {
550 maxSize = 0;
551 return value;
Philip Milne3f8956d2011-05-13 17:29:00 +0100552 } else {
Philip Milnef4748702011-06-09 18:30:32 -0700553 return defaultValue;
554 }
555 }
556
557 {
558 final boolean horizontal = (mOrientation == HORIZONTAL);
559 final int axis = horizontal ? mHorizontalAxis.count : mVerticalAxis.count;
560 final int count = valueIfDefined(axis, Integer.MAX_VALUE);
561
562 int row = 0;
563 int col = 0;
564 for (int i = 0, N = getChildCount(); i < N; i++) {
565 LayoutParams lp = getLayoutParams1(getChildAt(i));
566
567 Group colGroup = lp.columnGroup;
568 Interval cols = colGroup.span;
569 int colSpan = cols.size();
570
571 Group rowGroup = lp.rowGroup;
572 Interval rows = rowGroup.span;
573 int rowSpan = rows.size();
574
575 if (horizontal) {
576 row = valueIfDefined2(rows.min, row);
577
578 int newCol = valueIfDefined(cols.min, (col + colSpan > count) ? 0 : col);
579 if (newCol < col) {
580 row += maxSize;
581 maxSize = 0;
582 }
583 col = newCol;
584 maxSize = max(maxSize, rowSpan);
585 } else {
586 col = valueIfDefined2(cols.min, col);
587
588 int newRow = valueIfDefined(rows.min, (row + rowSpan > count) ? 0 : row);
589 if (newRow < row) {
590 col += maxSize;
591 maxSize = 0;
592 }
593 row = newRow;
594 maxSize = max(maxSize, colSpan);
595 }
596
597 lp.setColumnGroupSpan(new Interval(col, col + colSpan));
598 lp.setRowGroupSpan(new Interval(row, row + rowSpan));
599
600 if (horizontal) {
601 col = col + colSpan;
602 } else {
603 row = row + rowSpan;
Philip Milne3f8956d2011-05-13 17:29:00 +0100604 }
605 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100606 }
Philip Milnef4748702011-06-09 18:30:32 -0700607 };
608 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100609 }
610
611 private void invalidateStructure() {
612 mLayoutParamsValid = false;
613 mHorizontalAxis.invalidateStructure();
614 mVerticalAxis.invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100615 // This can end up being done twice. But better that than not at all.
616 invalidateValues();
617 }
618
619 private void invalidateValues() {
Philip Milneaa616f32011-05-27 18:38:01 -0700620 // Need null check because requestLayout() is called in View's initializer,
621 // before we are set up.
622 if (mHorizontalAxis != null && mVerticalAxis != null) {
623 mHorizontalAxis.invalidateValues();
624 mVerticalAxis.invalidateValues();
625 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100626 }
627
628 private LayoutParams getLayoutParams1(View c) {
629 return (LayoutParams) c.getLayoutParams();
630 }
631
632 private LayoutParams getLayoutParams(View c) {
633 if (!mLayoutParamsValid) {
634 validateLayoutParams();
635 mLayoutParamsValid = true;
636 }
637 return getLayoutParams1(c);
638 }
639
640 @Override
641 protected LayoutParams generateDefaultLayoutParams() {
642 return new LayoutParams();
643 }
644
645 @Override
646 public LayoutParams generateLayoutParams(AttributeSet attrs) {
647 return new LayoutParams(getContext(), attrs, mDefaultGravity);
648 }
649
650 @Override
651 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
652 return new LayoutParams(p);
653 }
654
655 // Draw grid
656
657 private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
658 int dx = getPaddingLeft();
659 int dy = getPaddingTop();
660 graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint);
661 }
662
663 @Override
664 protected void onDraw(Canvas canvas) {
665 super.onDraw(canvas);
666
667 if (DEBUG) {
668 int height = getHeight() - getPaddingTop() - getPaddingBottom();
669 int width = getWidth() - getPaddingLeft() - getPaddingRight();
670
671 int[] xs = mHorizontalAxis.locations;
672 for (int i = 0, length = xs.length; i < length; i++) {
673 int x = xs[i];
674 drawLine(canvas, x, 0, x, height - 1, GRID_PAINT);
675 }
676 int[] ys = mVerticalAxis.locations;
677 for (int i = 0, length = ys.length; i < length; i++) {
678 int y = ys[i];
679 drawLine(canvas, 0, y, width - 1, y, GRID_PAINT);
680 }
681 }
682 }
683
Philip Milne3f8956d2011-05-13 17:29:00 +0100684 // Add/remove
685
686 @Override
687 public void addView(View child, int index, ViewGroup.LayoutParams params) {
688 super.addView(child, index, params);
689 invalidateStructure();
690 }
691
692 @Override
693 public void removeView(View view) {
694 super.removeView(view);
695 invalidateStructure();
696 }
697
698 @Override
699 public void removeViewInLayout(View view) {
700 super.removeViewInLayout(view);
701 invalidateStructure();
702 }
703
704 @Override
705 public void removeViewsInLayout(int start, int count) {
706 super.removeViewsInLayout(start, count);
707 invalidateStructure();
708 }
709
710 @Override
711 public void removeViewAt(int index) {
712 super.removeViewAt(index);
713 invalidateStructure();
714 }
715
716 // Measurement
717
Philip Milneaa616f32011-05-27 18:38:01 -0700718 private static int getChildMeasureSpec2(int spec, int padding, int childDimension) {
719 int resultSize;
720 int resultMode;
721
722 if (childDimension >= 0) {
723 resultSize = childDimension;
724 resultMode = EXACTLY;
725 } else {
726 /*
727 using the following lines would replicate the logic of ViewGroup.getChildMeasureSpec()
728
729 int specMode = MeasureSpec.getMode(spec);
730 int specSize = MeasureSpec.getSize(spec);
731 int size = Math.max(0, specSize - padding);
732
733 resultSize = size;
734 resultMode = (specMode == EXACTLY && childDimension == LayoutParams.WRAP_CONTENT) ?
735 AT_MOST : specMode;
736 */
737 resultSize = 0;
738 resultMode = UNSPECIFIED;
739 }
740 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
741 }
742
743 @Override
744 protected void measureChild(View child, int parentWidthSpec, int parentHeightSpec) {
745 ViewGroup.LayoutParams lp = child.getLayoutParams();
746
747 int childWidthMeasureSpec = getChildMeasureSpec2(parentWidthSpec,
748 mPaddingLeft + mPaddingRight, lp.width);
749 int childHeightMeasureSpec = getChildMeasureSpec2(parentHeightSpec,
750 mPaddingTop + mPaddingBottom, lp.height);
751
752 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
753 }
754
Philip Milne3f8956d2011-05-13 17:29:00 +0100755 @Override
756 protected void onMeasure(int widthSpec, int heightSpec) {
Philip Milneaa616f32011-05-27 18:38:01 -0700757 measureChildren(widthSpec, heightSpec);
Philip Milne3f8956d2011-05-13 17:29:00 +0100758
Philip Milneaa616f32011-05-27 18:38:01 -0700759 int computedWidth = getPaddingLeft() + mHorizontalAxis.getMin() + getPaddingRight();
760 int computedHeight = getPaddingTop() + mVerticalAxis.getMin() + getPaddingBottom();
Philip Milne3f8956d2011-05-13 17:29:00 +0100761
762 setMeasuredDimension(
763 resolveSizeAndState(computedWidth, widthSpec, 0),
764 resolveSizeAndState(computedHeight, heightSpec, 0));
765 }
766
767 private int protect(int alignment) {
Philip Milneaa616f32011-05-27 18:38:01 -0700768 return (alignment == UNDEFINED) ? 0 : alignment;
Philip Milne3f8956d2011-05-13 17:29:00 +0100769 }
770
771 private int getMeasurement(View c, boolean horizontal, int measurementType) {
Philip Milneaa616f32011-05-27 18:38:01 -0700772 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
773 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100774
Philip Milneaa616f32011-05-27 18:38:01 -0700775 private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) {
776 int result = getMeasurement(c, horizontal, measurementType);
777 if (mMarginsIncludedInAlignment) {
778 int leadingMargin = getMargin(c, true, horizontal);
779 int trailingMargin = getMargin(c, false, horizontal);
780 return result + leadingMargin + trailingMargin;
Philip Milne3f8956d2011-05-13 17:29:00 +0100781 }
782 return result;
783 }
784
Philip Milne3f8956d2011-05-13 17:29:00 +0100785 @Override
Philip Milneaa616f32011-05-27 18:38:01 -0700786 public void requestLayout() {
787 super.requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100788 invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -0700789 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100790
Philip Milneaa616f32011-05-27 18:38:01 -0700791 // Layout container
792
793 /**
794 * {@inheritDoc}
795 */
796 /*
797 The layout operation is implemented by delegating the heavy lifting to the
798 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
799 Together they compute the locations of the vertical and horizontal lines of
800 the grid (respectively!).
801
802 This method is then left with the simpler task of applying margins, gravity
803 and sizing to each child view and then placing it in its cell.
804 */
805 @Override
806 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100807 int targetWidth = r - l;
808 int targetHeight = b - t;
809
810 int paddingLeft = getPaddingLeft();
811 int paddingTop = getPaddingTop();
812 int paddingRight = getPaddingRight();
813 int paddingBottom = getPaddingBottom();
814
815 mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
816 mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
817
818 for (int i = 0, size = getChildCount(); i < size; i++) {
819 View view = getChildAt(i);
Philip Milneaa616f32011-05-27 18:38:01 -0700820 LayoutParams lp = getLayoutParams(view);
821 Group columnGroup = lp.columnGroup;
822 Group rowGroup = lp.rowGroup;
Philip Milne3f8956d2011-05-13 17:29:00 +0100823
Philip Milneaa616f32011-05-27 18:38:01 -0700824 Interval colSpan = columnGroup.span;
825 Interval rowSpan = rowGroup.span;
Philip Milne3f8956d2011-05-13 17:29:00 +0100826
Philip Milneaa616f32011-05-27 18:38:01 -0700827 int x1 = mHorizontalAxis.getLocationIncludingMargin(view, true, colSpan.min);
828 int y1 = mVerticalAxis.getLocationIncludingMargin(view, true, rowSpan.min);
829
830 int x2 = mHorizontalAxis.getLocationIncludingMargin(view, false, colSpan.max);
831 int y2 = mVerticalAxis.getLocationIncludingMargin(view, false, rowSpan.max);
Philip Milne3f8956d2011-05-13 17:29:00 +0100832
833 int cellWidth = x2 - x1;
834 int cellHeight = y2 - y1;
835
Philip Milne3f8956d2011-05-13 17:29:00 +0100836 int pWidth = getMeasurement(view, true, PRF);
837 int pHeight = getMeasurement(view, false, PRF);
838
Philip Milneaa616f32011-05-27 18:38:01 -0700839 Alignment hAlignment = columnGroup.alignment;
840 Alignment vAlignment = rowGroup.alignment;
Philip Milne3f8956d2011-05-13 17:29:00 +0100841
Philip Milneaa616f32011-05-27 18:38:01 -0700842 int dx, dy;
Philip Milne3f8956d2011-05-13 17:29:00 +0100843
Philip Milne7fd94872011-06-07 20:14:17 -0700844 Bounds colBounds = mHorizontalAxis.getGroupBounds().getValue(i);
845 Bounds rowBounds = mVerticalAxis.getGroupBounds().getValue(i);
846
847 // Gravity offsets: the location of the alignment group relative to its cell group.
848 int c2ax = protect(hAlignment.getAlignmentValue(null, cellWidth - colBounds.size()));
849 int c2ay = protect(vAlignment.getAlignmentValue(null, cellHeight - rowBounds.size()));
850
Philip Milneaa616f32011-05-27 18:38:01 -0700851 if (mMarginsIncludedInAlignment) {
Philip Milne7fd94872011-06-07 20:14:17 -0700852 int leftMargin = getMargin(view, true, true);
853 int topMargin = getMargin(view, true, false);
854 int rightMargin = getMargin(view, false, true);
855 int bottomMargin = getMargin(view, false, false);
856
857 // Same calculation as getMeasurementIncludingMargin()
858 int measuredWidth = leftMargin + pWidth + rightMargin;
859 int measuredHeight = topMargin + pHeight + bottomMargin;
860
861 // Alignment offsets: the location of the view relative to its alignment group.
862 int a2vx = colBounds.before - hAlignment.getAlignmentValue(view, measuredWidth);
863 int a2vy = rowBounds.before - vAlignment.getAlignmentValue(view, measuredHeight);
864
865 dx = c2ax + a2vx + leftMargin;
866 dy = c2ay + a2vy + topMargin;
867
868 cellWidth -= leftMargin + rightMargin;
869 cellHeight -= topMargin + bottomMargin;
Philip Milneaa616f32011-05-27 18:38:01 -0700870 } else {
Philip Milne7fd94872011-06-07 20:14:17 -0700871 // Alignment offsets: the location of the view relative to its alignment group.
872 int a2vx = colBounds.before - hAlignment.getAlignmentValue(view, pWidth);
873 int a2vy = rowBounds.before - vAlignment.getAlignmentValue(view, pHeight);
Philip Milneaa616f32011-05-27 18:38:01 -0700874
Philip Milne7fd94872011-06-07 20:14:17 -0700875 dx = c2ax + a2vx;
876 dy = c2ay + a2vy;
Philip Milneaa616f32011-05-27 18:38:01 -0700877 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100878
879 int width = hAlignment.getSizeInCell(view, pWidth, cellWidth);
880 int height = vAlignment.getSizeInCell(view, pHeight, cellHeight);
881
882 int cx = paddingLeft + x1 + dx;
883 int cy = paddingTop + y1 + dy;
884 view.layout(cx, cy, cx + width, cy + height);
885 }
886 }
887
888 // Inner classes
889
Philip Milneaa616f32011-05-27 18:38:01 -0700890 /*
891 This internal class houses the algorithm for computing the locations of grid lines;
892 along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
893 distinguished by the "horizontal" flag which is true for the horizontal axis and false
894 for the vertical one.
895 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100896 private class Axis {
897 private static final int MIN_VALUE = -1000000;
Philip Milne3f8956d2011-05-13 17:29:00 +0100898
899 private static final int UNVISITED = 0;
900 private static final int PENDING = 1;
901 private static final int COMPLETE = 2;
902
903 public final boolean horizontal;
904
905 public int count = UNDEFINED;
906 public boolean countValid = false;
907 public boolean countWasExplicitySet = false;
908
909 PackedMap<Group, Bounds> groupBounds;
910 public boolean groupBoundsValid = false;
911
Philip Milneaa616f32011-05-27 18:38:01 -0700912 PackedMap<Interval, MutableInt> spanSizes;
Philip Milne3f8956d2011-05-13 17:29:00 +0100913 public boolean spanSizesValid = false;
914
Philip Milne3f8956d2011-05-13 17:29:00 +0100915 public int[] leadingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700916 public boolean leadingMarginsValid = false;
917
Philip Milne3f8956d2011-05-13 17:29:00 +0100918 public int[] trailingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700919 public boolean trailingMarginsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +0100920
921 public Arc[] arcs;
922 public boolean arcsValid = false;
923
Philip Milneaa616f32011-05-27 18:38:01 -0700924 public int[] minima;
925 public boolean minimaValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +0100926
Philip Milneaa616f32011-05-27 18:38:01 -0700927 public float[] weights;
928 public int[] locations;
929
930 private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED;
Philip Milne3f8956d2011-05-13 17:29:00 +0100931
932 private Axis(boolean horizontal) {
933 this.horizontal = horizontal;
934 }
935
Philip Milnef4748702011-06-09 18:30:32 -0700936 private int maxIndex() {
Philip Milne3f8956d2011-05-13 17:29:00 +0100937 // note the number Integer.MIN_VALUE + 1 comes up in undefined cells
938 int count = -1;
939 for (int i = 0, size = getChildCount(); i < size; i++) {
Philip Milnef4748702011-06-09 18:30:32 -0700940 LayoutParams params = getLayoutParams(getChildAt(i));
Philip Milne3f8956d2011-05-13 17:29:00 +0100941 Group g = horizontal ? params.columnGroup : params.rowGroup;
942 count = max(count, g.span.min);
943 count = max(count, g.span.max);
944 }
945 return count == -1 ? UNDEFINED : count;
946 }
947
Philip Milne3f8956d2011-05-13 17:29:00 +0100948 public int getCount() {
Philip Milnef4748702011-06-09 18:30:32 -0700949 if (!countValid) {
950 count = max(0, maxIndex()); // if there are no cells, the count is zero
Philip Milne3f8956d2011-05-13 17:29:00 +0100951 countValid = true;
952 }
953 return count;
954 }
955
956 public void setCount(int count) {
957 this.count = count;
958 this.countWasExplicitySet = count != UNDEFINED;
959 }
960
961 public boolean isOrderPreserved() {
962 return mOrderPreserved;
963 }
964
965 public void setOrderPreserved(boolean orderPreserved) {
966 mOrderPreserved = orderPreserved;
967 invalidateStructure();
968 }
969
970 private PackedMap<Group, Bounds> createGroupBounds() {
971 int N = getChildCount();
972 Group[] groups = new Group[N];
973 Bounds[] bounds = new Bounds[N];
974 for (int i = 0; i < N; i++) {
975 LayoutParams lp = getLayoutParams(getChildAt(i));
976 Group group = horizontal ? lp.columnGroup : lp.rowGroup;
977
978 groups[i] = group;
979 bounds[i] = new Bounds();
980 }
981
982 return new PackedMap<Group, Bounds>(groups, bounds);
983 }
984
985 private void computeGroupBounds() {
986 for (int i = 0; i < groupBounds.values.length; i++) {
987 groupBounds.values[i].reset();
988 }
Philip Milneaa616f32011-05-27 18:38:01 -0700989 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100990 View c = getChildAt(i);
991 LayoutParams lp = getLayoutParams(c);
992 Group g = horizontal ? lp.columnGroup : lp.rowGroup;
993
994 Bounds bounds = groupBounds.getValue(i);
Philip Milneaa616f32011-05-27 18:38:01 -0700995
996 int size = getMeasurementIncludingMargin(c, horizontal, PRF);
Philip Milne3f8956d2011-05-13 17:29:00 +0100997 // todo test this works correctly when the returned value is UNDEFINED
Philip Milne7fd94872011-06-07 20:14:17 -0700998 int before = g.alignment.getAlignmentValue(c, size);
999 bounds.include(before, size - before);
Philip Milne3f8956d2011-05-13 17:29:00 +01001000 }
1001 }
1002
1003 private PackedMap<Group, Bounds> getGroupBounds() {
1004 if (groupBounds == null) {
1005 groupBounds = createGroupBounds();
1006 }
1007 if (!groupBoundsValid) {
1008 computeGroupBounds();
1009 groupBoundsValid = true;
1010 }
1011 return groupBounds;
1012 }
1013
1014 // Add values computed by alignment - taking the max of all alignments in each span
Philip Milneaa616f32011-05-27 18:38:01 -07001015 private PackedMap<Interval, MutableInt> createSpanSizes() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001016 PackedMap<Group, Bounds> groupBounds = getGroupBounds();
1017 int N = groupBounds.keys.length;
1018 Interval[] spans = new Interval[N];
Philip Milneaa616f32011-05-27 18:38:01 -07001019 MutableInt[] values = new MutableInt[N];
Philip Milne3f8956d2011-05-13 17:29:00 +01001020 for (int i = 0; i < N; i++) {
1021 Interval key = groupBounds.keys[i].span;
1022
1023 spans[i] = key;
Philip Milneaa616f32011-05-27 18:38:01 -07001024 values[i] = new MutableInt();
Philip Milne3f8956d2011-05-13 17:29:00 +01001025 }
Philip Milneaa616f32011-05-27 18:38:01 -07001026 return new PackedMap<Interval, MutableInt>(spans, values);
Philip Milne3f8956d2011-05-13 17:29:00 +01001027 }
1028
1029 private void computeSpanSizes() {
Philip Milneaa616f32011-05-27 18:38:01 -07001030 MutableInt[] spans = spanSizes.values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001031 for (int i = 0; i < spans.length; i++) {
1032 spans[i].reset();
1033 }
1034
Philip Milneaa616f32011-05-27 18:38:01 -07001035 Bounds[] bounds = getGroupBounds().values; // use getter to trigger a re-evaluation
Philip Milne3f8956d2011-05-13 17:29:00 +01001036 for (int i = 0; i < bounds.length; i++) {
1037 int value = bounds[i].size();
1038
Philip Milneaa616f32011-05-27 18:38:01 -07001039 MutableInt valueHolder = spanSizes.getValue(i);
Philip Milne3f8956d2011-05-13 17:29:00 +01001040 valueHolder.value = max(valueHolder.value, value);
1041 }
1042 }
1043
Philip Milneaa616f32011-05-27 18:38:01 -07001044 private PackedMap<Interval, MutableInt> getSpanSizes() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001045 if (spanSizes == null) {
1046 spanSizes = createSpanSizes();
1047 }
1048 if (!spanSizesValid) {
1049 computeSpanSizes();
1050 spanSizesValid = true;
1051 }
1052 return spanSizes;
1053 }
1054
Philip Milneaa616f32011-05-27 18:38:01 -07001055 private void include(List<Arc> arcs, Interval key, MutableInt size) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001056 // this bit below should really be computed outside here -
1057 // its just to stop default (col>0) constraints obliterating valid entries
1058 for (Arc arc : arcs) {
1059 Interval span = arc.span;
1060 if (span.equals(key)) {
1061 return;
1062 }
1063 }
1064 arcs.add(new Arc(key, size));
1065 }
1066
Philip Milneaa616f32011-05-27 18:38:01 -07001067 private void include2(List<Arc> arcs, Interval span, MutableInt min, MutableInt max,
1068 boolean both) {
1069 include(arcs, span, min);
Philip Milne3f8956d2011-05-13 17:29:00 +01001070 if (both) {
Philip Milneaa616f32011-05-27 18:38:01 -07001071 // todo
1072// include(arcs, span.inverse(), max.neg());
Philip Milne3f8956d2011-05-13 17:29:00 +01001073 }
1074 }
1075
Philip Milneaa616f32011-05-27 18:38:01 -07001076 private void include2(List<Arc> arcs, Interval span, int min, int max, boolean both) {
1077 include2(arcs, span, new MutableInt(min), new MutableInt(max), both);
Philip Milne3f8956d2011-05-13 17:29:00 +01001078 }
1079
Philip Milneaa616f32011-05-27 18:38:01 -07001080 // Group arcs by their first vertex, returning an array of arrays.
Philip Milne3f8956d2011-05-13 17:29:00 +01001081 // This is linear in the number of arcs.
Philip Milneaa616f32011-05-27 18:38:01 -07001082 private Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001083 int N = getCount() + 1;// the number of vertices
1084 Arc[][] result = new Arc[N][];
1085 int[] sizes = new int[N];
1086 for (Arc arc : arcs) {
1087 sizes[arc.span.min]++;
1088 }
1089 for (int i = 0; i < sizes.length; i++) {
1090 result[i] = new Arc[sizes[i]];
1091 }
1092 // reuse the sizes array to hold the current last elements as we insert each arc
1093 Arrays.fill(sizes, 0);
1094 for (Arc arc : arcs) {
1095 int i = arc.span.min;
1096 result[i][sizes[i]++] = arc;
1097 }
1098
1099 return result;
1100 }
1101
Philip Milneaa616f32011-05-27 18:38:01 -07001102 /*
1103 Topological sort.
1104 */
1105 private Arc[] topologicalSort(final Arc[] arcs, int start) {
1106 // todo ensure the <start> vertex is added in edge cases
Philip Milne3f8956d2011-05-13 17:29:00 +01001107 final List<Arc> result = new ArrayList<Arc>();
1108 new Object() {
Philip Milneaa616f32011-05-27 18:38:01 -07001109 Arc[][] arcsByFirstVertex = groupArcsByFirstVertex(arcs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001110 int[] visited = new int[getCount() + 1];
1111
1112 boolean completesCycle(int loc) {
1113 int state = visited[loc];
1114 if (state == UNVISITED) {
1115 visited[loc] = PENDING;
Philip Milneaa616f32011-05-27 18:38:01 -07001116 for (Arc arc : arcsByFirstVertex[loc]) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001117 Interval span = arc.span;
1118 // the recursive call
1119 if (completesCycle(span.max)) {
1120 // which arcs get set here is dependent on the order
1121 // in which we explore nodes
1122 arc.completesCycle = true;
1123 }
1124 result.add(arc);
1125 }
1126 visited[loc] = COMPLETE;
1127 } else if (state == PENDING) {
1128 return true;
1129 } else if (state == COMPLETE) {
1130 }
1131 return false;
1132 }
1133 }.completesCycle(start);
1134 Collections.reverse(result);
1135 assert arcs.length == result.size();
1136 return result.toArray(new Arc[result.size()]);
1137 }
1138
1139 private boolean[] findUsed(Collection<Arc> arcs) {
1140 boolean[] result = new boolean[getCount()];
1141 for (Arc arc : arcs) {
1142 Interval span = arc.span;
1143 int min = min(span.min, span.max);
1144 int max = max(span.min, span.max);
1145 for (int i = min; i < max; i++) {
1146 result[i] = true;
1147 }
1148 }
1149 return result;
1150 }
1151
Philip Milneaa616f32011-05-27 18:38:01 -07001152 // todo unify with findUsed above. Both routines analyze which rows/columns are empty.
Philip Milne3f8956d2011-05-13 17:29:00 +01001153 private Collection<Interval> getSpacers() {
1154 List<Interval> result = new ArrayList<Interval>();
1155 int N = getCount() + 1;
1156 int[] leadingEdgeCount = new int[N];
1157 int[] trailingEdgeCount = new int[N];
1158 for (int i = 0, size = getChildCount(); i < size; i++) {
1159 LayoutParams lp = getLayoutParams(getChildAt(i));
1160 Group g = horizontal ? lp.columnGroup : lp.rowGroup;
1161 Interval span = g.span;
1162 leadingEdgeCount[span.min]++;
1163 trailingEdgeCount[span.max]++;
1164 }
1165
1166 int lastTrailingEdge = 0;
1167
1168 // treat the parent's edges like peer edges of the opposite type
1169 trailingEdgeCount[0] = 1;
1170 leadingEdgeCount[N - 1] = 1;
1171
1172 for (int i = 0; i < N; i++) {
1173 if (trailingEdgeCount[i] > 0) {
1174 lastTrailingEdge = i;
1175 continue; // if this is also a leading edge, don't add a space of length zero
1176 }
1177 if (leadingEdgeCount[i] > 0) {
1178 result.add(new Interval(lastTrailingEdge, i));
1179 }
1180 }
1181 return result;
1182 }
1183
Philip Milneaa616f32011-05-27 18:38:01 -07001184 private Arc[] createArcs() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001185 List<Arc> spanToSize = new ArrayList<Arc>();
1186
1187 // Add all the preferred elements that were not defined by the user.
Philip Milneaa616f32011-05-27 18:38:01 -07001188 PackedMap<Interval, MutableInt> spanSizes = getSpanSizes();
Philip Milne3f8956d2011-05-13 17:29:00 +01001189 for (int i = 0; i < spanSizes.keys.length; i++) {
1190 Interval key = spanSizes.keys[i];
Philip Milneaa616f32011-05-27 18:38:01 -07001191 MutableInt value = spanSizes.values[i];
Philip Milne3f8956d2011-05-13 17:29:00 +01001192 // todo remove value duplicate
Philip Milneaa616f32011-05-27 18:38:01 -07001193 include2(spanToSize, key, value, value, accommodateBothMinAndMax);
Philip Milne3f8956d2011-05-13 17:29:00 +01001194 }
1195
1196 // Find redundant rows/cols and glue them together with 0-length arcs to link the tree
1197 boolean[] used = findUsed(spanToSize);
1198 for (int i = 0; i < getCount(); i++) {
1199 if (!used[i]) {
1200 Interval span = new Interval(i, i + 1);
Philip Milneaa616f32011-05-27 18:38:01 -07001201 include(spanToSize, span, new MutableInt(0));
1202 include(spanToSize, span.inverse(), new MutableInt(0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001203 }
1204 }
1205
1206 if (mOrderPreserved) {
1207 // Add preferred gaps
1208 for (int i = 0; i < getCount(); i++) {
1209 if (used[i]) {
Philip Milneaa616f32011-05-27 18:38:01 -07001210 include2(spanToSize, new Interval(i, i + 1), 0, 0, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001211 }
1212 }
1213 } else {
1214 for (Interval gap : getSpacers()) {
Philip Milneaa616f32011-05-27 18:38:01 -07001215 include2(spanToSize, gap, 0, 0, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001216 }
1217 }
1218 Arc[] arcs = spanToSize.toArray(new Arc[spanToSize.size()]);
Philip Milneaa616f32011-05-27 18:38:01 -07001219 return topologicalSort(arcs, 0);
Philip Milne3f8956d2011-05-13 17:29:00 +01001220 }
1221
Philip Milneaa616f32011-05-27 18:38:01 -07001222 public Arc[] getArcs() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001223 if (arcs == null) {
Philip Milneaa616f32011-05-27 18:38:01 -07001224 arcs = createArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001225 }
1226 if (!arcsValid) {
1227 getSpanSizes();
1228 arcsValid = true;
1229 }
1230 return arcs;
1231 }
1232
Philip Milneaa616f32011-05-27 18:38:01 -07001233 private boolean relax(int[] locations, Arc entry) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001234 Interval span = entry.span;
1235 int u = span.min;
1236 int v = span.max;
1237 int value = entry.value.value;
1238 int candidate = locations[u] + value;
Philip Milneaa616f32011-05-27 18:38:01 -07001239 if (candidate > locations[v]) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001240 locations[v] = candidate;
1241 return true;
1242 }
1243 return false;
1244 }
1245
Philip Milneaa616f32011-05-27 18:38:01 -07001246 /*
1247 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
1248
1249 GridLayout converts its requirements into a system of linear constraints of the
1250 form:
1251
1252 x[i] - x[j] < a[k]
1253
1254 Where the x[i] are variables and the a[k] are constants.
1255
1256 For example, if the variables were instead labeled x, y, z we might have:
1257
1258 x - y < 17
1259 y - z < 23
1260 z - x < 42
1261
1262 This is a special case of the Linear Programming problem that is, in turn,
1263 equivalent to the single-source shortest paths problem on a digraph, for
1264 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
1265
1266 Other algorithms are faster in the case where no arcs have negative weights
1267 but allowing negative weights turns out to be the same as accommodating maximum
1268 size requirements as well as minimum ones.
1269
1270 Bellman-Ford works by iteratively 'relaxing' constraints over all nodes (an O(N)
1271 process) and performing this step N times. Proof of correctness hinges on the
1272 fact that there can be no negative weight chains of length > N - unless a
1273 'negative weight loop' exists. The algorithm catches this case in a final
1274 checking phase that reports failure.
1275
1276 By topologically sorting the nodes and checking this condition at each step
1277 typical layout problems complete after the first iteration and the algorithm
1278 completes in O(N) steps with very low constants.
1279 */
1280 private int[] solve(Arc[] arcs, int[] locations) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001281 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
1282
1283 boolean changed = false;
1284 // We take one extra pass over traditional Bellman-Ford (and omit their final step)
1285 for (int i = 0; i < N; i++) {
1286 changed = false;
1287 for (int j = 0, length = arcs.length; j < length; j++) {
Philip Milneaa616f32011-05-27 18:38:01 -07001288 changed = changed | relax(locations, arcs[j]);
Philip Milne3f8956d2011-05-13 17:29:00 +01001289 }
1290 if (!changed) {
1291 if (DEBUG) {
Philip Milneaa616f32011-05-27 18:38:01 -07001292 Log.d(TAG, "Iteration " +
Philip Milne3f8956d2011-05-13 17:29:00 +01001293 " completed after " + (1 + i) + " steps out of " + N);
1294 }
1295 break;
1296 }
1297 }
1298 if (changed) {
1299 Log.d(TAG, "*** Algorithm failed to terminate ***");
1300 }
1301 return locations;
1302 }
1303
Philip Milneaa616f32011-05-27 18:38:01 -07001304 private void computeMargins(boolean leading) {
1305 int[] margins = leading ? leadingMargins : trailingMargins;
Philip Milne3f8956d2011-05-13 17:29:00 +01001306 for (int i = 0, size = getChildCount(); i < size; i++) {
1307 View c = getChildAt(i);
1308 LayoutParams lp = getLayoutParams(c);
1309 Group g = horizontal ? lp.columnGroup : lp.rowGroup;
1310 Interval span = g.span;
1311 int index = leading ? span.min : span.max;
Philip Milneaa616f32011-05-27 18:38:01 -07001312 margins[index] = max(margins[index], getMargin(c, leading, horizontal));
Philip Milne3f8956d2011-05-13 17:29:00 +01001313 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001314 }
1315
Philip Milneaa616f32011-05-27 18:38:01 -07001316 private int[] getLeadingMargins() {
1317 if (leadingMargins == null) {
1318 leadingMargins = new int[getCount() + 1];
1319 }
1320 if (!leadingMarginsValid) {
1321 computeMargins(true);
1322 leadingMarginsValid = true;
1323 }
1324 return leadingMargins;
1325 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001326
Philip Milneaa616f32011-05-27 18:38:01 -07001327 private int[] getTrailingMargins() {
1328 if (trailingMargins == null) {
1329 trailingMargins = new int[getCount() + 1];
1330 }
1331 if (!trailingMarginsValid) {
1332 computeMargins(false);
1333 trailingMarginsValid = true;
1334 }
1335 return trailingMargins;
1336 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001337
Philip Milneaa616f32011-05-27 18:38:01 -07001338 private void addMargins() {
1339 int[] leadingMargins = getLeadingMargins();
1340 int[] trailingMargins = getTrailingMargins();
1341
Philip Milne3f8956d2011-05-13 17:29:00 +01001342 int delta = 0;
Philip Milneaa616f32011-05-27 18:38:01 -07001343 for (int i = 0, N = getCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001344 int margins = leadingMargins[i] + trailingMargins[i + 1];
1345 delta += margins;
Philip Milneaa616f32011-05-27 18:38:01 -07001346 minima[i + 1] += delta;
Philip Milne3f8956d2011-05-13 17:29:00 +01001347 }
1348 }
1349
Philip Milneaa616f32011-05-27 18:38:01 -07001350 private int getLocationIncludingMargin(View view, boolean leading, int index) {
1351 int location = locations[index];
1352 int margin;
1353 if (!mMarginsIncludedInAlignment) {
1354 margin = (leading ? leadingMargins : trailingMargins)[index];
1355 } else {
Philip Milne7fd94872011-06-07 20:14:17 -07001356 margin = 0;
Philip Milneaa616f32011-05-27 18:38:01 -07001357 }
1358 return leading ? (location + margin) : (location - margin);
1359 }
1360
1361 private void computeMinima(int[] a) {
1362 Arrays.fill(a, MIN_VALUE);
1363 a[0] = 0;
1364 solve(getArcs(), a);
1365 if (!mMarginsIncludedInAlignment) {
1366 addMargins();
1367 }
1368 }
1369
1370 private int[] getMinima() {
1371 if (minima == null) {
1372 int N = getCount() + 1;
1373 minima = new int[N];
1374 }
1375 if (!minimaValid) {
1376 computeMinima(minima);
1377 minimaValid = true;
1378 }
1379 return minima;
1380 }
1381
1382 private void computeWeights() {
1383 for (int i = 0, N = getChildCount(); i < N; i++) {
1384 LayoutParams lp = getLayoutParams(getChildAt(i));
1385 Group g = horizontal ? lp.columnGroup : lp.rowGroup;
1386 Interval span = g.span;
1387 int penultimateIndex = span.max - 1;
1388 weights[penultimateIndex] += horizontal ? lp.columnWeight : lp.rowWeight;
1389 }
1390 }
1391
1392 private float[] getWeights() {
1393 if (weights == null) {
Philip Milnef4748702011-06-09 18:30:32 -07001394 int N = getCount();
Philip Milneaa616f32011-05-27 18:38:01 -07001395 weights = new float[N];
1396 }
1397 computeWeights();
1398 return weights;
1399 }
1400
1401 private int[] getLocations() {
1402 if (locations == null) {
1403 int N = getCount() + 1;
1404 locations = new int[N];
1405 }
1406 return locations;
1407 }
1408
1409 // External entry points
1410
Philip Milne3f8956d2011-05-13 17:29:00 +01001411 private int size(int[] locations) {
1412 return locations[locations.length - 1] - locations[0];
1413 }
1414
Philip Milne3f8956d2011-05-13 17:29:00 +01001415 private int getMin() {
Philip Milneaa616f32011-05-27 18:38:01 -07001416 return size(getMinima());
Philip Milne3f8956d2011-05-13 17:29:00 +01001417 }
1418
1419 private void layout(int targetSize) {
Philip Milneaa616f32011-05-27 18:38:01 -07001420 int[] mins = getMinima();
Philip Milne3f8956d2011-05-13 17:29:00 +01001421
Philip Milneaa616f32011-05-27 18:38:01 -07001422 int totalDelta = max(0, targetSize - size(mins)); // confine to expansion
Philip Milne3f8956d2011-05-13 17:29:00 +01001423
Philip Milneaa616f32011-05-27 18:38:01 -07001424 float[] weights = getWeights();
1425 float totalWeight = sum(weights);
1426
Philip Milnef4748702011-06-09 18:30:32 -07001427 if (totalWeight == 0f && weights.length > 0) {
Philip Milneaa616f32011-05-27 18:38:01 -07001428 weights[weights.length - 1] = 1;
1429 totalWeight = 1;
Philip Milne3f8956d2011-05-13 17:29:00 +01001430 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001431
Philip Milneaa616f32011-05-27 18:38:01 -07001432 int[] locations = getLocations();
1433 int cumulativeDelta = 0;
1434
Philip Milnef4748702011-06-09 18:30:32 -07001435 // note |weights| = |locations| - 1
1436 for (int i = 0; i < weights.length; i++) {
Philip Milneaa616f32011-05-27 18:38:01 -07001437 float weight = weights[i];
1438 int delta = (int) (totalDelta * weight / totalWeight);
1439 cumulativeDelta += delta;
Philip Milnef4748702011-06-09 18:30:32 -07001440 locations[i + 1] = mins[i + 1] + cumulativeDelta;
Philip Milneaa616f32011-05-27 18:38:01 -07001441
1442 totalDelta -= delta;
1443 totalWeight -= weight;
Philip Milne3f8956d2011-05-13 17:29:00 +01001444 }
1445 }
1446
1447 private void invalidateStructure() {
1448 countValid = false;
Philip Milneaa616f32011-05-27 18:38:01 -07001449
Philip Milne3f8956d2011-05-13 17:29:00 +01001450 groupBounds = null;
1451 spanSizes = null;
Philip Milneaa616f32011-05-27 18:38:01 -07001452 leadingMargins = null;
1453 trailingMargins = null;
1454 minima = null;
1455 weights = null;
1456 locations = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001457
1458 invalidateValues();
1459 }
1460
1461 private void invalidateValues() {
1462 groupBoundsValid = false;
1463 spanSizesValid = false;
1464 arcsValid = false;
Philip Milneaa616f32011-05-27 18:38:01 -07001465 leadingMarginsValid = false;
1466 trailingMarginsValid = false;
1467 minimaValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001468 }
1469 }
1470
1471 /**
1472 * Layout information associated with each of the children of a GridLayout.
1473 * <p>
1474 * GridLayout supports both row and column spanning and arbitrary forms of alignment within
1475 * each cell group. The fundamental parameters associated with each cell group are
1476 * gathered into their vertical and horizontal components and stored
1477 * in the {@link #rowGroup} and {@link #columnGroup} layout parameters.
1478 * {@link Group Groups} are immutable structures and may be shared between the layout
1479 * parameters of different children.
1480 * <p>
Philip Milneaa616f32011-05-27 18:38:01 -07001481 * The row and column groups contain the leading and trailing indices along each axis
1482 * and together specify the four grid indices that delimit the cells of this cell group.
Philip Milne3f8956d2011-05-13 17:29:00 +01001483 * <p>
1484 * The {@link Group#alignment alignment} fields of the row and column groups together specify
1485 * both aspects of alignment within the cell group. It is also possible to specify a child's
1486 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
1487 * method.
1488 * <p>
1489 * See {@link GridLayout} for a description of the conventions used by GridLayout
1490 * in reference to grid indices.
1491 *
1492 * <h4>Default values</h4>
1493 *
1494 * <ul>
1495 * <li>{@link #width} = {@link #WRAP_CONTENT}</li>
1496 * <li>{@link #height} = {@link #WRAP_CONTENT}</li>
1497 * <li>{@link #topMargin} = 0 when
1498 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001499 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001500 * indicate that a default value should be computed on demand. </li>
1501 * <li>{@link #leftMargin} = 0 when
1502 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001503 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001504 * indicate that a default value should be computed on demand. </li>
1505 * <li>{@link #bottomMargin} = 0 when
1506 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001507 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001508 * indicate that a default value should be computed on demand. </li>
1509 * <li>{@link #rightMargin} = 0 when
1510 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001511 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001512 * indicate that a default value should be computed on demand. </li>
Philip Milne7fd94872011-06-07 20:14:17 -07001513 * <li>{@link #rowGroup}{@code .span} = {@code [0, 1]} </li>
1514 * <li>{@link #rowGroup}{@code .alignment} = {@link #BASELINE} </li>
1515 * <li>{@link #columnGroup}{@code .span} = {@code [0, 1]} </li>
1516 * <li>{@link #columnGroup}{@code .alignment} = {@link #LEFT} </li>
1517 * <li>{@link #rowWeight} = {@code 0f} </li>
1518 * <li>{@link #columnWeight} = {@code 0f} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01001519 * </ul>
1520 *
1521 * @attr ref android.R.styleable#GridLayout_Layout_layout_row
1522 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
1523 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
1524 * @attr ref android.R.styleable#GridLayout_Layout_layout_column
1525 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
1526 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
1527 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
1528 */
1529 public static class LayoutParams extends MarginLayoutParams {
1530
1531 // Default values
1532
1533 private static final int DEFAULT_WIDTH = WRAP_CONTENT;
1534 private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
1535 private static final int DEFAULT_MARGIN = UNDEFINED;
1536 private static final int DEFAULT_ROW = UNDEFINED;
1537 private static final int DEFAULT_COLUMN = UNDEFINED;
Philip Milnef4748702011-06-09 18:30:32 -07001538 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01001539 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
Philip Milnef4748702011-06-09 18:30:32 -07001540 private static final Alignment DEFAULT_COLUMN_ALIGNMENT = LEFT;
1541 private static final Alignment DEFAULT_ROW_ALIGNMENT = BASELINE;
1542 private static final Group DEFAULT_COLUMN_GROUP =
1543 new Group(DEFAULT_SPAN, DEFAULT_COLUMN_ALIGNMENT);
1544 private static final Group DEFAULT_ROW_GROUP =
1545 new Group(DEFAULT_SPAN, DEFAULT_ROW_ALIGNMENT);
Philip Milneaa616f32011-05-27 18:38:01 -07001546 private static final int DEFAULT_WEIGHT_0 = 0;
1547 private static final int DEFAULT_WEIGHT_1 = 1;
Philip Milne3f8956d2011-05-13 17:29:00 +01001548
1549 // Misc
1550
1551 private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2);
Philip Milnef4748702011-06-09 18:30:32 -07001552 private static final Alignment[] COLUMN_ALIGNMENTS = { LEFT, CENTER, RIGHT };
1553 private static final Alignment[] ROW_ALIGNMENTS = { TOP, CENTER, BOTTOM };
Philip Milne3f8956d2011-05-13 17:29:00 +01001554
1555 // TypedArray indices
1556
1557 private static final int MARGIN = styleable.ViewGroup_MarginLayout_layout_margin;
1558 private static final int LEFT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginLeft;
1559 private static final int TOP_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginTop;
1560 private static final int RIGHT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginRight;
1561 private static final int BOTTOM_MARGIN =
1562 styleable.ViewGroup_MarginLayout_layout_marginBottom;
1563
1564 private static final int COLUMN = styleable.GridLayout_Layout_layout_column;
1565 private static final int COLUMN_SPAN = styleable.GridLayout_Layout_layout_columnSpan;
1566 private static final int COLUMN_WEIGHT = styleable.GridLayout_Layout_layout_columnWeight;
1567 private static final int ROW = styleable.GridLayout_Layout_layout_row;
1568 private static final int ROW_SPAN = styleable.GridLayout_Layout_layout_rowSpan;
1569 private static final int ROW_WEIGHT = styleable.GridLayout_Layout_layout_rowWeight;
1570 private static final int GRAVITY = styleable.GridLayout_Layout_layout_gravity;
1571
1572 // Instance variables
1573
1574 /**
1575 * The group that specifies the vertical characteristics of the cell group
1576 * described by these layout parameters.
1577 */
1578 public Group rowGroup;
1579 /**
1580 * The group that specifies the horizontal characteristics of the cell group
1581 * described by these layout parameters.
1582 */
1583 public Group columnGroup;
1584 /**
1585 * The proportional space that should be taken by the associated row group
1586 * during excess space distribution.
1587 */
1588 public float rowWeight;
1589 /**
1590 * The proportional space that should be taken by the associated column group
1591 * during excess space distribution.
1592 */
1593 public float columnWeight;
1594
1595 // Constructors
1596
1597 private LayoutParams(
1598 int width, int height,
1599 int left, int top, int right, int bottom,
1600 Group rowGroup, Group columnGroup, float rowWeight, float columnWeight) {
1601 super(width, height);
1602 setMargins(left, top, right, bottom);
1603 this.rowGroup = rowGroup;
1604 this.columnGroup = columnGroup;
1605 this.rowWeight = rowWeight;
1606 this.columnWeight = columnWeight;
1607 }
1608
1609 /**
1610 * Constructs a new LayoutParams instance for this <code>rowGroup</code>
1611 * and <code>columnGroup</code>. All other fields are initialized with
1612 * default values as defined in {@link LayoutParams}.
1613 *
1614 * @param rowGroup the rowGroup
1615 * @param columnGroup the columnGroup
1616 */
1617 public LayoutParams(Group rowGroup, Group columnGroup) {
1618 this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
1619 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
Philip Milneaa616f32011-05-27 18:38:01 -07001620 rowGroup, columnGroup, DEFAULT_WEIGHT_0, DEFAULT_WEIGHT_0);
Philip Milne3f8956d2011-05-13 17:29:00 +01001621 }
1622
1623 /**
1624 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
1625 */
1626 public LayoutParams() {
Philip Milnef4748702011-06-09 18:30:32 -07001627 this(DEFAULT_ROW_GROUP, DEFAULT_COLUMN_GROUP);
Philip Milne3f8956d2011-05-13 17:29:00 +01001628 }
1629
1630 // Copying constructors
1631
1632 /**
1633 * {@inheritDoc}
1634 */
1635 public LayoutParams(ViewGroup.LayoutParams params) {
1636 super(params);
1637 }
1638
1639 /**
1640 * {@inheritDoc}
1641 */
1642 public LayoutParams(MarginLayoutParams params) {
1643 super(params);
1644 }
1645
1646 /**
1647 * {@inheritDoc}
1648 */
1649 public LayoutParams(LayoutParams that) {
1650 super(that);
1651 this.columnGroup = that.columnGroup;
1652 this.rowGroup = that.rowGroup;
1653 this.columnWeight = that.columnWeight;
1654 this.rowWeight = that.rowWeight;
1655 }
1656
1657 // AttributeSet constructors
1658
1659 private LayoutParams(Context context, AttributeSet attrs, int defaultGravity) {
1660 super(context, attrs);
1661 reInitSuper(context, attrs);
1662 init(context, attrs, defaultGravity);
1663 }
1664
1665 /**
1666 * {@inheritDoc}
1667 *
1668 * Values not defined in the attribute set take the default values
1669 * defined in {@link LayoutParams}.
1670 */
1671 public LayoutParams(Context context, AttributeSet attrs) {
1672 this(context, attrs, Gravity.NO_GRAVITY);
1673 }
1674
1675 // Implementation
1676
1677 private static boolean definesVertical(int gravity) {
1678 return gravity > 0 && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != 0;
1679 }
1680
1681 private static boolean definesHorizontal(int gravity) {
1682 return gravity > 0 && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 0;
1683 }
1684
1685 private static <T> T getAlignment(T[] alignments, T fill, int min, int max,
1686 boolean isUndefined, T defaultValue) {
1687 if (isUndefined) {
1688 return defaultValue;
1689 }
1690 return min != max ? fill : alignments[min];
1691 }
1692
1693 // Reinitialise the margins using a different default policy than MarginLayoutParams.
1694 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
1695 // so that a layout manager default can be accessed post set up. We need this as, at the
1696 // point of installation, we do not know how many rows/cols there are and therefore
1697 // which elements are positioned next to the container's trailing edges. We need to
1698 // know this as margins around the container's boundary should have different
1699 // defaults to those between peers.
1700
1701 // This method could be parametrized and moved into MarginLayout.
1702 private void reInitSuper(Context context, AttributeSet attrs) {
1703 TypedArray a = context.obtainStyledAttributes(attrs, styleable.ViewGroup_MarginLayout);
1704 try {
1705 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
1706
1707 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
1708 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
1709 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
1710 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
1711 } finally {
1712 a.recycle();
1713 }
1714 }
1715
1716 // Gravity. For conversion from the static the integers defined in the Gravity class,
1717 // use Gravity.apply() to apply gravity to a view of zero size and see where it ends up.
Philip Milnef4748702011-06-09 18:30:32 -07001718 private static Alignment getColumnAlignment(int gravity, int width) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001719 Rect r = new Rect(0, 0, 0, 0);
1720 Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r);
1721
Philip Milnef4748702011-06-09 18:30:32 -07001722 boolean fill = (width == MATCH_PARENT);
1723 Alignment defaultAlignment = fill ? FILL : DEFAULT_COLUMN_ALIGNMENT;
1724 return getAlignment(COLUMN_ALIGNMENTS, FILL, r.left, r.right,
Philip Milne3f8956d2011-05-13 17:29:00 +01001725 !definesHorizontal(gravity), defaultAlignment);
1726 }
1727
Philip Milnef4748702011-06-09 18:30:32 -07001728 private static Alignment getRowAlignment(int gravity, int height) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001729 Rect r = new Rect(0, 0, 0, 0);
1730 Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r);
1731
Philip Milnef4748702011-06-09 18:30:32 -07001732 boolean fill = (height == MATCH_PARENT);
1733 Alignment defaultAlignment = fill ? FILL : DEFAULT_ROW_ALIGNMENT;
1734 return getAlignment(ROW_ALIGNMENTS, FILL, r.top, r.bottom,
Philip Milne3f8956d2011-05-13 17:29:00 +01001735 !definesVertical(gravity), defaultAlignment);
1736 }
1737
Philip Milneaa616f32011-05-27 18:38:01 -07001738 private int getDefaultWeight(int size) {
1739 return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0;
1740 }
1741
Philip Milne3f8956d2011-05-13 17:29:00 +01001742 private void init(Context context, AttributeSet attrs, int defaultGravity) {
1743 TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout_Layout);
1744 try {
1745 int gravity = a.getInteger(GRAVITY, defaultGravity);
1746
1747 int column = a.getInteger(COLUMN, DEFAULT_COLUMN);
Philip Milneaa616f32011-05-27 18:38:01 -07001748 int columnSpan = a.getInteger(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
1749 Interval hSpan = new Interval(column, column + columnSpan);
Philip Milnef4748702011-06-09 18:30:32 -07001750 this.columnGroup = new Group(hSpan, getColumnAlignment(gravity, width));
Philip Milneaa616f32011-05-27 18:38:01 -07001751 this.columnWeight = a.getFloat(COLUMN_WEIGHT, getDefaultWeight(width));
Philip Milne3f8956d2011-05-13 17:29:00 +01001752
1753 int row = a.getInteger(ROW, DEFAULT_ROW);
Philip Milneaa616f32011-05-27 18:38:01 -07001754 int rowSpan = a.getInteger(ROW_SPAN, DEFAULT_SPAN_SIZE);
1755 Interval vSpan = new Interval(row, row + rowSpan);
Philip Milnef4748702011-06-09 18:30:32 -07001756 this.rowGroup = new Group(vSpan, getRowAlignment(gravity, height));
Philip Milneaa616f32011-05-27 18:38:01 -07001757 this.rowWeight = a.getFloat(ROW_WEIGHT, getDefaultWeight(height));
Philip Milne3f8956d2011-05-13 17:29:00 +01001758 } finally {
1759 a.recycle();
1760 }
1761 }
1762
1763 /**
Philip Milne7fd94872011-06-07 20:14:17 -07001764 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
1765 * See {@link android.view.Gravity}.
Philip Milne3f8956d2011-05-13 17:29:00 +01001766 *
Philip Milne7fd94872011-06-07 20:14:17 -07001767 * @param gravity the new gravity value
Philip Milne3f8956d2011-05-13 17:29:00 +01001768 *
1769 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
1770 */
1771 public void setGravity(int gravity) {
Philip Milnef4748702011-06-09 18:30:32 -07001772 columnGroup = columnGroup.copyWriteAlignment(getColumnAlignment(gravity, width));
1773 rowGroup = rowGroup.copyWriteAlignment(getRowAlignment(gravity, height));
Philip Milne3f8956d2011-05-13 17:29:00 +01001774 }
1775
1776 @Override
1777 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
1778 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
1779 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
1780 }
1781
Philip Milnef4748702011-06-09 18:30:32 -07001782 private void setRowGroupSpan(Interval span) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001783 rowGroup = rowGroup.copyWriteSpan(span);
1784 }
1785
Philip Milnef4748702011-06-09 18:30:32 -07001786 private void setColumnGroupSpan(Interval span) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001787 columnGroup = columnGroup.copyWriteSpan(span);
1788 }
1789 }
1790
Philip Milneaa616f32011-05-27 18:38:01 -07001791 /*
1792 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
1793 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
1794 */
Philip Milne3f8956d2011-05-13 17:29:00 +01001795 private static class Arc {
1796 public final Interval span;
Philip Milneaa616f32011-05-27 18:38:01 -07001797 public final MutableInt value;
Philip Milne3f8956d2011-05-13 17:29:00 +01001798 public boolean completesCycle;
1799
Philip Milneaa616f32011-05-27 18:38:01 -07001800 public Arc(Interval span, MutableInt value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001801 this.span = span;
1802 this.value = value;
1803 }
1804
1805 @Override
1806 public String toString() {
1807 return span + " " + (completesCycle ? "+>" : "->") + " " + value;
1808 }
1809 }
1810
1811 // A mutable Integer - used to avoid heap allocation during the layout operation
1812
Philip Milneaa616f32011-05-27 18:38:01 -07001813 private static class MutableInt {
Philip Milne3f8956d2011-05-13 17:29:00 +01001814 public int value;
1815
Philip Milneaa616f32011-05-27 18:38:01 -07001816 private MutableInt() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001817 reset();
1818 }
1819
Philip Milneaa616f32011-05-27 18:38:01 -07001820 private MutableInt(int value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001821 this.value = value;
1822 }
1823
1824 private void reset() {
1825 value = Integer.MIN_VALUE;
1826 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001827 }
1828
Philip Milneaa616f32011-05-27 18:38:01 -07001829 /*
1830 This data structure is used in place of a Map where we have an index that refers to the order
1831 in which each key/value pairs were added to the map. In this case we store keys and values
1832 in arrays of a length that is equal to the number of unique keys. We also maintain an
1833 array of indexes from insertion order to the compacted arrays of keys and values.
1834
1835 Note that behavior differs from that of a LinkedHashMap in that repeated entries
1836 *do* get added multiples times. So the length of index is equals to the number of
1837 items added.
1838
1839 This is useful in the GridLayout class where we can rely on the order of children not
1840 changing during layout - to use integer-based lookup for our internal structures
1841 rather than using (and storing) an implementation of Map<Key, ?>.
1842 */
Philip Milne3f8956d2011-05-13 17:29:00 +01001843 @SuppressWarnings(value = "unchecked")
1844 private static class PackedMap<K, V> {
1845 public final int[] index;
1846 public final K[] keys;
1847 public final V[] values;
1848
1849 private PackedMap(K[] keys, V[] values) {
1850 this.index = createIndex(keys);
1851
Philip Milneaa616f32011-05-27 18:38:01 -07001852 this.keys = compact(keys, index);
1853 this.values = compact(values, index);
Philip Milne3f8956d2011-05-13 17:29:00 +01001854 }
1855
1856 private K getKey(int i) {
1857 return keys[index[i]];
1858 }
1859
1860 private V getValue(int i) {
1861 return values[index[i]];
1862 }
1863
1864 private static <K> int[] createIndex(K[] keys) {
1865 int size = keys.length;
1866 int[] result = new int[size];
1867
1868 Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
1869 for (int i = 0; i < size; i++) {
1870 K key = keys[i];
1871 Integer index = keyToIndex.get(key);
1872 if (index == null) {
1873 index = keyToIndex.size();
1874 keyToIndex.put(key, index);
1875 }
1876 result[i] = index;
1877 }
1878 return result;
1879 }
1880
1881 private static int max(int[] a, int valueIfEmpty) {
1882 int result = valueIfEmpty;
1883 for (int i = 0, length = a.length; i < length; i++) {
1884 result = Math.max(result, a[i]);
1885 }
1886 return result;
1887 }
1888
Philip Milneaa616f32011-05-27 18:38:01 -07001889 /*
1890 Create a compact array of keys or values using the supplied index.
1891 */
1892 private static <K> K[] compact(K[] a, int[] index) {
1893 int size = a.length;
1894 Class<?> componentType = a.getClass().getComponentType();
Philip Milne3f8956d2011-05-13 17:29:00 +01001895 K[] result = (K[]) Array.newInstance(componentType, max(index, -1) + 1);
1896
1897 // this overwrite duplicates, retaining the last equivalent entry
1898 for (int i = 0; i < size; i++) {
Philip Milneaa616f32011-05-27 18:38:01 -07001899 result[index[i]] = a[i];
Philip Milne3f8956d2011-05-13 17:29:00 +01001900 }
1901 return result;
1902 }
1903 }
1904
Philip Milneaa616f32011-05-27 18:38:01 -07001905 /*
1906 For each Group (with a given alignment) we need to store the amount of space required
Philip Milne7fd94872011-06-07 20:14:17 -07001907 before the alignment point and the amount of space required after it. One side of this
Philip Milneaa616f32011-05-27 18:38:01 -07001908 calculation is always 0 for LEADING and TRAILING alignments but we don't make use of this.
1909 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
1910 simple optimisations are possible.
1911
1912 The general algorithm therefore is to create a Map (actually a PackedMap) from
1913 Group to Bounds and to loop through all Views in the group taking the maximum
1914 of the values for each View.
1915 */
Philip Milne3f8956d2011-05-13 17:29:00 +01001916 private static class Bounds {
Philip Milne7fd94872011-06-07 20:14:17 -07001917 public int before;
1918 public int after;
Philip Milne3f8956d2011-05-13 17:29:00 +01001919
1920 private Bounds() {
1921 reset();
1922 }
1923
1924 private void reset() {
Philip Milne7fd94872011-06-07 20:14:17 -07001925 before = Integer.MIN_VALUE;
1926 after = Integer.MIN_VALUE;
Philip Milne3f8956d2011-05-13 17:29:00 +01001927 }
1928
Philip Milne7fd94872011-06-07 20:14:17 -07001929 private void include(int before, int after) {
1930 this.before = max(this.before, before);
1931 this.after = max(this.after, after);
Philip Milne3f8956d2011-05-13 17:29:00 +01001932 }
1933
1934 private int size() {
Philip Milne7fd94872011-06-07 20:14:17 -07001935 return before + after;
Philip Milne3f8956d2011-05-13 17:29:00 +01001936 }
1937
1938 @Override
1939 public String toString() {
1940 return "Bounds{" +
Philip Milne7fd94872011-06-07 20:14:17 -07001941 "before=" + before +
1942 ", after=" + after +
Philip Milne3f8956d2011-05-13 17:29:00 +01001943 '}';
1944 }
1945 }
1946
1947 /**
1948 * An Interval represents a contiguous range of values that lie between
1949 * the interval's {@link #min} and {@link #max} values.
1950 * <p>
1951 * Intervals are immutable so may be passed as values and used as keys in hash tables.
1952 * It is not necessary to have multiple instances of Intervals which have the same
1953 * {@link #min} and {@link #max} values.
1954 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -07001955 * Intervals are often written as {@code [min, max]} and represent the set of values
1956 * {@code x} such that {@code min <= x < max}.
Philip Milne3f8956d2011-05-13 17:29:00 +01001957 */
Philip Milneaa616f32011-05-27 18:38:01 -07001958 /* package */ static class Interval {
Philip Milne3f8956d2011-05-13 17:29:00 +01001959 /**
1960 * The minimum value.
1961 */
1962 public final int min;
Philip Milneaa616f32011-05-27 18:38:01 -07001963
Philip Milne3f8956d2011-05-13 17:29:00 +01001964 /**
1965 * The maximum value.
1966 */
1967 public final int max;
1968
1969 /**
Philip Milne7fd94872011-06-07 20:14:17 -07001970 * Construct a new Interval, {@code interval}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01001971 * <ul>
Philip Milne7fd94872011-06-07 20:14:17 -07001972 * <li> {@code interval.min = min} </li>
1973 * <li> {@code interval.max = max} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01001974 * </ul>
1975 *
1976 * @param min the minimum value.
1977 * @param max the maximum value.
1978 */
1979 public Interval(int min, int max) {
1980 this.min = min;
1981 this.max = max;
1982 }
1983
1984 private int size() {
1985 return max - min;
1986 }
1987
1988 private Interval inverse() {
1989 return new Interval(max, min);
1990 }
1991
1992 /**
Philip Milne7fd94872011-06-07 20:14:17 -07001993 * Returns {@code true} if the {@link #getClass class},
1994 * {@link #min} and {@link #max} properties of this Interval and the
1995 * supplied parameter are pairwise equal; {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01001996 *
Philip Milne7fd94872011-06-07 20:14:17 -07001997 * @param that the object to compare this interval with
Philip Milne3f8956d2011-05-13 17:29:00 +01001998 *
1999 * @return {@code true} if the specified object is equal to this
Philip Milne7fd94872011-06-07 20:14:17 -07002000 * {@code Interval}, {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002001 */
2002 @Override
2003 public boolean equals(Object that) {
2004 if (this == that) {
2005 return true;
2006 }
2007 if (that == null || getClass() != that.getClass()) {
2008 return false;
2009 }
2010
2011 Interval interval = (Interval) that;
2012
2013 if (max != interval.max) {
2014 return false;
2015 }
2016 if (min != interval.min) {
2017 return false;
2018 }
2019
2020 return true;
2021 }
2022
2023 @Override
2024 public int hashCode() {
2025 int result = min;
2026 result = 31 * result + max;
2027 return result;
2028 }
2029
2030 @Override
2031 public String toString() {
2032 return "[" + min + ", " + max + "]";
2033 }
2034 }
2035
2036 /**
2037 * A group specifies either the horizontal or vertical characteristics of a group of
2038 * cells.
2039 * <p>
2040 * Groups are immutable and so may be shared between views with the same
Philip Milne7fd94872011-06-07 20:14:17 -07002041 * {@code span} and {@code alignment}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002042 */
2043 public static class Group {
2044 /**
Philip Milneaa616f32011-05-27 18:38:01 -07002045 * The grid indices of the leading and trailing edges of this cell group for the
2046 * appropriate axis.
Philip Milne3f8956d2011-05-13 17:29:00 +01002047 * <p>
2048 * See {@link GridLayout} for a description of the conventions used by GridLayout
2049 * for grid indices.
2050 */
Philip Milneaa616f32011-05-27 18:38:01 -07002051 /* package */ final Interval span;
Philip Milne3f8956d2011-05-13 17:29:00 +01002052 /**
2053 * Specifies how cells should be aligned in this group.
2054 * For row groups, this specifies the vertical alignment.
2055 * For column groups, this specifies the horizontal alignment.
2056 */
2057 public final Alignment alignment;
2058
2059 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002060 * Construct a new Group, {@code group}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002061 * <ul>
Philip Milne7fd94872011-06-07 20:14:17 -07002062 * <li> {@code group.span = span} </li>
2063 * <li> {@code group.alignment = alignment} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002064 * </ul>
2065 *
Philip Milne7fd94872011-06-07 20:14:17 -07002066 * @param span the span
2067 * @param alignment the alignment
Philip Milne3f8956d2011-05-13 17:29:00 +01002068 */
Philip Milneaa616f32011-05-27 18:38:01 -07002069 /* package */ Group(Interval span, Alignment alignment) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002070 this.span = span;
2071 this.alignment = alignment;
2072 }
2073
2074 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002075 * Construct a new Group, {@code group}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002076 * <ul>
Philip Milnef4748702011-06-09 18:30:32 -07002077 * <li> {@code group.span = [start, start + size]} </li>
Philip Milne7fd94872011-06-07 20:14:17 -07002078 * <li> {@code group.alignment = alignment} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002079 * </ul>
2080 *
Philip Milnef4748702011-06-09 18:30:32 -07002081 * @param start the start
2082 * @param size the size
Philip Milne7fd94872011-06-07 20:14:17 -07002083 * @param alignment the alignment
Philip Milne3f8956d2011-05-13 17:29:00 +01002084 */
Philip Milnef4748702011-06-09 18:30:32 -07002085 public Group(int start, int size, Alignment alignment) {
2086 this(new Interval(start, start + size), alignment);
Philip Milne3f8956d2011-05-13 17:29:00 +01002087 }
2088
2089 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002090 * Construct a new Group, {@code group}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002091 * <ul>
Philip Milnef4748702011-06-09 18:30:32 -07002092 * <li> {@code group.span = [start, start + 1]} </li>
Philip Milne7fd94872011-06-07 20:14:17 -07002093 * <li> {@code group.alignment = alignment} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002094 * </ul>
2095 *
Philip Milnef4748702011-06-09 18:30:32 -07002096 * @param start the start index
Philip Milne7fd94872011-06-07 20:14:17 -07002097 * @param alignment the alignment
Philip Milne3f8956d2011-05-13 17:29:00 +01002098 */
Philip Milnef4748702011-06-09 18:30:32 -07002099 public Group(int start, Alignment alignment) {
2100 this(start, 1, alignment);
Philip Milne3f8956d2011-05-13 17:29:00 +01002101 }
2102
2103 private Group copyWriteSpan(Interval span) {
2104 return new Group(span, alignment);
2105 }
2106
2107 private Group copyWriteAlignment(Alignment alignment) {
2108 return new Group(span, alignment);
2109 }
2110
2111 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002112 * Returns {@code true} if the {@link #getClass class}, {@link #alignment} and {@code span}
2113 * properties of this Group and the supplied parameter are pairwise equal,
2114 * {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002115 *
Philip Milne7fd94872011-06-07 20:14:17 -07002116 * @param that the object to compare this group with
Philip Milne3f8956d2011-05-13 17:29:00 +01002117 *
2118 * @return {@code true} if the specified object is equal to this
Philip Milne7fd94872011-06-07 20:14:17 -07002119 * {@code Group}; {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +01002120 */
2121 @Override
2122 public boolean equals(Object that) {
2123 if (this == that) {
2124 return true;
2125 }
2126 if (that == null || getClass() != that.getClass()) {
2127 return false;
2128 }
2129
2130 Group group = (Group) that;
2131
2132 if (!alignment.equals(group.alignment)) {
2133 return false;
2134 }
2135 if (!span.equals(group.span)) {
2136 return false;
2137 }
2138
2139 return true;
2140 }
2141
2142 @Override
2143 public int hashCode() {
2144 int result = span.hashCode();
2145 result = 31 * result + alignment.hashCode();
2146 return result;
2147 }
2148 }
2149
Philip Milne3f8956d2011-05-13 17:29:00 +01002150 /**
2151 * Alignments specify where a view should be placed within a cell group and
2152 * what size it should be.
2153 * <p>
2154 * The {@link LayoutParams} class contains a {@link LayoutParams#rowGroup rowGroup}
2155 * and a {@link LayoutParams#columnGroup columnGroup} each of which contains an
2156 * {@link Group#alignment alignment}. Overall placement of the view in the cell
2157 * group is specified by the two alignments which act along each axis independently.
2158 * <p>
2159 * An Alignment implementation must define the {@link #getAlignmentValue(View, int)}
2160 * to return the appropriate value for the type of alignment being defined.
2161 * The enclosing algorithms position the children
2162 * so that the values returned from the alignment
2163 * are the same for all of the views in a group.
2164 * <p>
2165 * The GridLayout class defines the most common alignments used in general layout:
2166 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link
2167 * #BASELINE} and {@link #FILL}.
2168 */
2169 public static interface Alignment {
2170 /**
2171 * Returns an alignment value. In the case of vertical alignments the value
2172 * returned should indicate the distance from the top of the view to the
2173 * alignment location.
2174 * For horizontal alignments measurement is made from the left edge of the component.
2175 *
Philip Milne7fd94872011-06-07 20:14:17 -07002176 * @param view the view to which this alignment should be applied
2177 * @param viewSize the measured size of the view
2178 * @return the alignment value
Philip Milne3f8956d2011-05-13 17:29:00 +01002179 */
2180 public int getAlignmentValue(View view, int viewSize);
2181
2182 /**
2183 * Returns the size of the view specified by this alignment.
2184 * In the case of vertical alignments this method should return a height; for
2185 * horizontal alignments this method should return the width.
2186 *
Philip Milne7fd94872011-06-07 20:14:17 -07002187 * @param view the view to which this alignment should be applied
2188 * @param viewSize the measured size of the view
2189 * @param cellSize the size of the cell into which this view will be placed
2190 * @return the aligned size
Philip Milne3f8956d2011-05-13 17:29:00 +01002191 */
2192 public int getSizeInCell(View view, int viewSize, int cellSize);
2193 }
2194
2195 private static abstract class AbstractAlignment implements Alignment {
2196 public int getSizeInCell(View view, int viewSize, int cellSize) {
2197 return viewSize;
2198 }
2199 }
2200
2201 private static final Alignment LEADING = new AbstractAlignment() {
2202 public int getAlignmentValue(View view, int viewSize) {
2203 return 0;
2204 }
2205
2206 };
2207
2208 private static final Alignment TRAILING = new AbstractAlignment() {
2209 public int getAlignmentValue(View view, int viewSize) {
2210 return viewSize;
2211 }
2212 };
2213
2214 /**
2215 * Indicates that a view should be aligned with the <em>top</em>
2216 * edges of the other views in its cell group.
2217 */
2218 public static final Alignment TOP = LEADING;
2219
2220 /**
2221 * Indicates that a view should be aligned with the <em>bottom</em>
2222 * edges of the other views in its cell group.
2223 */
2224 public static final Alignment BOTTOM = TRAILING;
2225
2226 /**
2227 * Indicates that a view should be aligned with the <em>right</em>
2228 * edges of the other views in its cell group.
2229 */
2230 public static final Alignment RIGHT = TRAILING;
2231
2232 /**
2233 * Indicates that a view should be aligned with the <em>left</em>
2234 * edges of the other views in its cell group.
2235 */
2236 public static final Alignment LEFT = LEADING;
2237
2238 /**
2239 * Indicates that a view should be <em>centered</em> with the other views in its cell group.
2240 * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and {@link
2241 * LayoutParams#columnGroup columnGroups}.
2242 */
2243 public static final Alignment CENTER = new AbstractAlignment() {
2244 public int getAlignmentValue(View view, int viewSize) {
2245 return viewSize >> 1;
2246 }
2247 };
2248
2249 /**
2250 * Indicates that a view should be aligned with the <em>baselines</em>
2251 * of the other views in its cell group.
2252 * This constant may only be used as an alignment in {@link LayoutParams#rowGroup rowGroups}.
2253 *
2254 * @see View#getBaseline()
2255 */
2256 public static final Alignment BASELINE = new AbstractAlignment() {
Philip Milne7fd94872011-06-07 20:14:17 -07002257 public int getAlignmentValue(View view, int height) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002258 if (view == null) {
2259 return UNDEFINED;
2260 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002261 int baseline = view.getBaseline();
Philip Milne7fd94872011-06-07 20:14:17 -07002262 if (baseline == -1) {
2263 return UNDEFINED;
2264 } else {
2265 return baseline;
2266 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002267 }
2268
2269 };
2270
2271 /**
2272 * Indicates that a view should expanded to fit the boundaries of its cell group.
2273 * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and
2274 * {@link LayoutParams#columnGroup columnGroups}.
2275 */
2276 public static final Alignment FILL = new Alignment() {
2277 public int getAlignmentValue(View view, int viewSize) {
2278 return UNDEFINED;
2279 }
2280
2281 public int getSizeInCell(View view, int viewSize, int cellSize) {
2282 return cellSize;
2283 }
2284 };
2285}