blob: 851160163e59a7c6981d7887f811e14064005e33 [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
Tor Norbyed9273d62013-05-30 15:59:53 -070019import android.annotation.IntDef;
Philip Milne3f8956d2011-05-13 17:29:00 +010020import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Color;
Philip Milne1557fd72012-04-04 23:41:34 -070024import android.graphics.Insets;
Philip Milne3f8956d2011-05-13 17:29:00 +010025import android.graphics.Paint;
Philip Milne3f8956d2011-05-13 17:29:00 +010026import android.util.AttributeSet;
27import android.util.Log;
Philip Milne211d0332012-04-24 14:45:14 -070028import android.util.LogPrinter;
Philip Milne48b55242011-06-29 11:09:45 -070029import android.util.Pair;
Philip Milne211d0332012-04-24 14:45:14 -070030import android.util.Printer;
Philip Milne3f8956d2011-05-13 17:29:00 +010031import android.view.Gravity;
32import android.view.View;
33import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080034import android.view.accessibility.AccessibilityEvent;
35import android.view.accessibility.AccessibilityNodeInfo;
Philip Milneedd69512012-03-14 17:21:33 -070036import android.widget.RemoteViews.RemoteView;
Philip Milne10ca24a2012-04-23 15:38:27 -070037import com.android.internal.R;
Philip Milne3f8956d2011-05-13 17:29:00 +010038
Tor Norbyed9273d62013-05-30 15:59:53 -070039import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
Philip Milne3f8956d2011-05-13 17:29:00 +010041import java.lang.reflect.Array;
42import java.util.ArrayList;
43import java.util.Arrays;
Philip Milne3f8956d2011-05-13 17:29:00 +010044import java.util.HashMap;
45import java.util.List;
46import java.util.Map;
47
Philip Milne5125e212011-07-21 11:39:37 -070048import static android.view.Gravity.*;
Philip Milne899d5922011-07-21 11:39:37 -070049import static android.view.View.MeasureSpec.EXACTLY;
50import static android.view.View.MeasureSpec.makeMeasureSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +010051import static java.lang.Math.max;
52import static java.lang.Math.min;
53
54/**
55 * A layout that places its children in a rectangular <em>grid</em>.
56 * <p>
57 * The grid is composed of a set of infinitely thin lines that separate the
58 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
Philip Milne7fd94872011-06-07 20:14:17 -070059 * by grid <em>indices</em>. A grid with {@code N} columns
60 * has {@code N + 1} grid indices that run from {@code 0}
61 * through {@code N} inclusive. Regardless of how GridLayout is
62 * configured, grid index {@code 0} is fixed to the leading edge of the
63 * container and grid index {@code N} is fixed to its trailing edge
Philip Milne3f8956d2011-05-13 17:29:00 +010064 * (after padding is taken into account).
65 *
Philip Milne93cd6a62011-07-12 14:49:45 -070066 * <h4>Row and Column Specs</h4>
Philip Milne3f8956d2011-05-13 17:29:00 +010067 *
68 * Children occupy one or more contiguous cells, as defined
Philip Milne93cd6a62011-07-12 14:49:45 -070069 * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and
70 * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters.
71 * Each spec defines the set of rows or columns that are to be
Philip Milne3f8956d2011-05-13 17:29:00 +010072 * occupied; and how children should be aligned within the resulting group of cells.
73 * Although cells do not normally overlap in a GridLayout, GridLayout does
74 * not prevent children being defined to occupy the same cell or group of cells.
75 * In this case however, there is no guarantee that children will not themselves
76 * overlap after the layout operation completes.
77 *
78 * <h4>Default Cell Assignment</h4>
79 *
Philip Milne48b55242011-06-29 11:09:45 -070080 * If a child does not specify the row and column indices of the cell it
Philip Milne3f8956d2011-05-13 17:29:00 +010081 * wishes to occupy, GridLayout assigns cell locations automatically using its:
82 * {@link GridLayout#setOrientation(int) orientation},
83 * {@link GridLayout#setRowCount(int) rowCount} and
84 * {@link GridLayout#setColumnCount(int) columnCount} properties.
85 *
86 * <h4>Space</h4>
87 *
88 * Space between children may be specified either by using instances of the
89 * dedicated {@link Space} view or by setting the
90 *
91 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
92 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
93 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
94 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
95 *
96 * layout parameters. When the
97 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
98 * property is set, default margins around children are automatically
Philip Milnef6679c82011-09-18 11:36:57 -070099 * allocated based on the prevailing UI style guide for the platform.
100 * Each of the margins so defined may be independently overridden by an assignment
Philip Milne3f8956d2011-05-13 17:29:00 +0100101 * to the appropriate layout parameter.
Philip Milnef6679c82011-09-18 11:36:57 -0700102 * Default values will generally produce a reasonable spacing between components
103 * but values may change between different releases of the platform.
Philip Milne3f8956d2011-05-13 17:29:00 +0100104 *
105 * <h4>Excess Space Distribution</h4>
106 *
Philip Milnef6679c82011-09-18 11:36:57 -0700107 * GridLayout's distribution of excess space is based on <em>priority</em>
108 * rather than <em>weight</em>.
109 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700110 * A child's ability to stretch is inferred from the alignment properties of
111 * its row and column groups (which are typically set by setting the
112 * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
113 * If alignment was defined along a given axis then the component
Philip Milnef6679c82011-09-18 11:36:57 -0700114 * is taken as <em>flexible</em> in that direction. If no alignment was set,
115 * the component is instead assumed to be <em>inflexible</em>.
116 * <p>
117 * Multiple components in the same row or column group are
118 * considered to act in <em>parallel</em>. Such a
Philip Milne899d5922011-07-21 11:39:37 -0700119 * group is flexible only if <em>all</em> of the components
120 * within it are flexible. Row and column groups that sit either side of a common boundary
121 * are instead considered to act in <em>series</em>. The composite group made of these two
122 * elements is flexible if <em>one</em> of its elements is flexible.
123 * <p>
124 * To make a column stretch, make sure all of the components inside it define a
125 * gravity. To prevent a column from stretching, ensure that one of the components
126 * in the column does not define a gravity.
Philip Milne3f8956d2011-05-13 17:29:00 +0100127 * <p>
Philip Milnef6679c82011-09-18 11:36:57 -0700128 * When the principle of flexibility does not provide complete disambiguation,
129 * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
130 * and <em>bottom</em> edges.
131 *
Philip Milnea8416442013-02-25 10:49:39 -0800132 * <h4>Interpretation of GONE</h4>
133 *
134 * For layout purposes, GridLayout treats views whose visibility status is
135 * {@link View#GONE GONE}, as having zero width and height. This is subtly different from
136 * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked
137 * view was alone in a column, that column would itself collapse to zero width if and only if
138 * no gravity was defined on the view. If gravity was defined, then the gone-marked
139 * view has no effect on the layout and the container should be laid out as if the view
140 * had never been added to it.
141 * These statements apply equally to rows as well as columns, and to groups of rows or columns.
142 *
Philip Milnef6679c82011-09-18 11:36:57 -0700143 * <h5>Limitations</h5>
144 *
145 * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
146 * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
Philip Milne6216e872012-02-16 17:15:50 -0800147 * to configure a GridLayout to distribute excess space between multiple components.
Philip Milnef6679c82011-09-18 11:36:57 -0700148 * <p>
149 * Some common use-cases may nevertheless be accommodated as follows.
150 * To place equal amounts of space around a component in a cell group;
151 * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
152 * For complete control over excess space distribution in a row or column;
153 * use a {@link LinearLayout} subview to hold the components in the associated cell group.
154 * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
Philip Milne3f8956d2011-05-13 17:29:00 +0100155 * <p>
156 * See {@link GridLayout.LayoutParams} for a full description of the
157 * layout parameters used by GridLayout.
158 *
159 * @attr ref android.R.styleable#GridLayout_orientation
160 * @attr ref android.R.styleable#GridLayout_rowCount
161 * @attr ref android.R.styleable#GridLayout_columnCount
162 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
163 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
164 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
165 */
Philip Milneedd69512012-03-14 17:21:33 -0700166@RemoteView
Philip Milne3f8956d2011-05-13 17:29:00 +0100167public class GridLayout extends ViewGroup {
168
169 // Public constants
170
Tor Norbyed9273d62013-05-30 15:59:53 -0700171 /** @hide */
172 @IntDef({HORIZONTAL, VERTICAL})
173 @Retention(RetentionPolicy.SOURCE)
174 public @interface Orientation {}
175
Philip Milne3f8956d2011-05-13 17:29:00 +0100176 /**
177 * The horizontal orientation.
178 */
179 public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
Philip Milneaa616f32011-05-27 18:38:01 -0700180
Philip Milne3f8956d2011-05-13 17:29:00 +0100181 /**
182 * The vertical orientation.
183 */
184 public static final int VERTICAL = LinearLayout.VERTICAL;
185
Philip Milneaa616f32011-05-27 18:38:01 -0700186 /**
187 * The constant used to indicate that a value is undefined.
188 * Fields can use this value to indicate that their values
189 * have not yet been set. Similarly, methods can return this value
190 * to indicate that there is no suitable value that the implementation
191 * can return.
192 * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
193 * intended to avoid confusion between valid values whose sign may not be known.
194 */
195 public static final int UNDEFINED = Integer.MIN_VALUE;
196
Tor Norbyed9273d62013-05-30 15:59:53 -0700197 /** @hide */
198 @IntDef({ALIGN_BOUNDS, ALIGN_MARGINS})
199 @Retention(RetentionPolicy.SOURCE)
200 public @interface AlignmentMode {}
201
Philip Milne1e548252011-06-16 19:02:33 -0700202 /**
203 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
204 * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
205 * is made between the edges of each component's raw
206 * view boundary: i.e. the area delimited by the component's:
207 * {@link android.view.View#getTop() top},
208 * {@link android.view.View#getLeft() left},
209 * {@link android.view.View#getBottom() bottom} and
210 * {@link android.view.View#getRight() right} properties.
211 * <p>
212 * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode,
213 * children that belong to a row group that uses {@link #TOP} alignment will
214 * all return the same value when their {@link android.view.View#getTop()}
215 * method is called.
216 *
217 * @see #setAlignmentMode(int)
218 */
219 public static final int ALIGN_BOUNDS = 0;
220
221 /**
222 * This constant is an {@link #setAlignmentMode(int) alignmentMode}.
223 * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS},
224 * the bounds of each view are extended outwards, according
225 * to their margins, before the edges of the resulting rectangle are aligned.
226 * <p>
227 * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode,
228 * the quantity {@code top - layoutParams.topMargin} is the same for all children that
229 * belong to a row group that uses {@link #TOP} alignment.
230 *
231 * @see #setAlignmentMode(int)
232 */
233 public static final int ALIGN_MARGINS = 1;
234
Philip Milne3f8956d2011-05-13 17:29:00 +0100235 // Misc constants
236
Philip Milnef6679c82011-09-18 11:36:57 -0700237 static final int MAX_SIZE = 100000;
238 static final int DEFAULT_CONTAINER_MARGIN = 0;
Philip Milned7dd8902012-01-26 16:55:30 -0800239 static final int UNINITIALIZED_HASH = 0;
Philip Milnea2353622013-03-05 12:19:15 -0800240 static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName());
241 static final Printer NO_PRINTER = new Printer() {
242 @Override
243 public void println(String x) {
244 }
245 };
Philip Milne3f8956d2011-05-13 17:29:00 +0100246
247 // Defaults
248
249 private static final int DEFAULT_ORIENTATION = HORIZONTAL;
250 private static final int DEFAULT_COUNT = UNDEFINED;
251 private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
Philip Milne899d5922011-07-21 11:39:37 -0700252 private static final boolean DEFAULT_ORDER_PRESERVED = true;
Philip Milne1e548252011-06-16 19:02:33 -0700253 private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
Philip Milne3f8956d2011-05-13 17:29:00 +0100254
255 // TypedArray indices
256
Philip Milneb0ce49b2011-07-15 15:39:07 -0700257 private static final int ORIENTATION = R.styleable.GridLayout_orientation;
258 private static final int ROW_COUNT = R.styleable.GridLayout_rowCount;
259 private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount;
260 private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins;
261 private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode;
262 private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved;
263 private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +0100264
265 // Instance variables
266
Adam Powell465ea742013-08-29 14:56:51 -0700267 final Axis mHorizontalAxis = new Axis(true);
268 final Axis mVerticalAxis = new Axis(false);
269 int mOrientation = DEFAULT_ORIENTATION;
270 boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
271 int mAlignmentMode = DEFAULT_ALIGNMENT_MODE;
272 int mDefaultGap;
273 int mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
274 Printer mPrinter = LOG_PRINTER;
Philip Milneaa616f32011-05-27 18:38:01 -0700275
Philip Milne3f8956d2011-05-13 17:29:00 +0100276 // Constructors
277
Alan Viveretted6479ec2013-09-10 17:03:02 -0700278 public GridLayout(Context context) {
279 this(context, null);
280 }
281
282 public GridLayout(Context context, AttributeSet attrs) {
283 this(context, attrs, 0);
284 }
285
Alan Viverette617feb92013-09-09 18:09:13 -0700286 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
287 this(context, attrs, defStyleAttr, 0);
288 }
289
290 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
291 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell465ea742013-08-29 14:56:51 -0700292 mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
Alan Viverette617feb92013-09-09 18:09:13 -0700293 final TypedArray a = context.obtainStyledAttributes(
294 attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes);
Philip Milne3f8956d2011-05-13 17:29:00 +0100295 try {
Philip Milne1e548252011-06-16 19:02:33 -0700296 setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
297 setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
Philip Milne5125e212011-07-21 11:39:37 -0700298 setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION));
299 setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS));
300 setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE));
Philip Milne3f8956d2011-05-13 17:29:00 +0100301 setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
302 setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
303 } finally {
304 a.recycle();
305 }
306 }
307
308 // Implementation
309
310 /**
311 * Returns the current orientation.
312 *
Philip Milne7fd94872011-06-07 20:14:17 -0700313 * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100314 *
315 * @see #setOrientation(int)
316 *
317 * @attr ref android.R.styleable#GridLayout_orientation
318 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700319 @Orientation
Philip Milne3f8956d2011-05-13 17:29:00 +0100320 public int getOrientation() {
Adam Powell465ea742013-08-29 14:56:51 -0700321 return mOrientation;
Philip Milne3f8956d2011-05-13 17:29:00 +0100322 }
323
324 /**
Philip Milneb65408f2012-05-21 10:44:46 -0700325 *
326 * GridLayout uses the orientation property for two purposes:
327 * <ul>
328 * <li>
329 * To control the 'direction' in which default row/column indices are generated
330 * when they are not specified in a component's layout parameters.
331 * </li>
332 * <li>
333 * To control which axis should be processed first during the layout operation:
334 * when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first.
335 * </li>
336 * </ul>
337 *
338 * The order in which axes are laid out is important if, for example, the height of
339 * one of GridLayout's children is dependent on its width - and its width is, in turn,
340 * dependent on the widths of other components.
341 * <p>
342 * If your layout contains a {@link TextView} (or derivative:
343 * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is
344 * in multi-line mode (the default) it is normally best to leave GridLayout's
345 * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of
346 * deriving its height for a given width, but not the other way around.
347 * <p>
348 * Other than the effects above, orientation does not affect the actual layout operation of
349 * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if
350 * the height of the intended layout greatly exceeds its width.
Philip Milne7fd94872011-06-07 20:14:17 -0700351 * <p>
352 * The default value of this property is {@link #HORIZONTAL}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100353 *
Philip Milne7fd94872011-06-07 20:14:17 -0700354 * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
Philip Milne3f8956d2011-05-13 17:29:00 +0100355 *
356 * @see #getOrientation()
357 *
358 * @attr ref android.R.styleable#GridLayout_orientation
359 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700360 public void setOrientation(@Orientation int orientation) {
Adam Powell465ea742013-08-29 14:56:51 -0700361 if (this.mOrientation != orientation) {
362 this.mOrientation = orientation;
Philip Milnef6679c82011-09-18 11:36:57 -0700363 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100364 requestLayout();
365 }
366 }
367
368 /**
369 * Returns the current number of rows. This is either the last value that was set
370 * with {@link #setRowCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700371 * value of each the upper bounds defined in {@link LayoutParams#rowSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100372 *
373 * @return the current number of rows
374 *
375 * @see #setRowCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700376 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100377 *
378 * @attr ref android.R.styleable#GridLayout_rowCount
379 */
380 public int getRowCount() {
Adam Powell465ea742013-08-29 14:56:51 -0700381 return mVerticalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100382 }
383
384 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700385 * RowCount is used only to generate default row/column indices when
386 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100387 *
Philip Milne7fd94872011-06-07 20:14:17 -0700388 * @param rowCount the number of rows
Philip Milne3f8956d2011-05-13 17:29:00 +0100389 *
390 * @see #getRowCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700391 * @see LayoutParams#rowSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100392 *
393 * @attr ref android.R.styleable#GridLayout_rowCount
394 */
395 public void setRowCount(int rowCount) {
Adam Powell465ea742013-08-29 14:56:51 -0700396 mVerticalAxis.setCount(rowCount);
Philip Milnef6679c82011-09-18 11:36:57 -0700397 invalidateStructure();
398 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100399 }
400
401 /**
402 * Returns the current number of columns. This is either the last value that was set
403 * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
Philip Milne93cd6a62011-07-12 14:49:45 -0700404 * value of each the upper bounds defined in {@link LayoutParams#columnSpec}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100405 *
406 * @return the current number of columns
407 *
408 * @see #setColumnCount(int)
Philip Milne93cd6a62011-07-12 14:49:45 -0700409 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100410 *
411 * @attr ref android.R.styleable#GridLayout_columnCount
412 */
413 public int getColumnCount() {
Adam Powell465ea742013-08-29 14:56:51 -0700414 return mHorizontalAxis.getCount();
Philip Milne3f8956d2011-05-13 17:29:00 +0100415 }
416
417 /**
Philip Milnef6679c82011-09-18 11:36:57 -0700418 * ColumnCount is used only to generate default column/column indices when
419 * they are not specified by a component's layout parameters.
Philip Milne3f8956d2011-05-13 17:29:00 +0100420 *
421 * @param columnCount the number of columns.
422 *
423 * @see #getColumnCount()
Philip Milne93cd6a62011-07-12 14:49:45 -0700424 * @see LayoutParams#columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +0100425 *
426 * @attr ref android.R.styleable#GridLayout_columnCount
427 */
428 public void setColumnCount(int columnCount) {
Adam Powell465ea742013-08-29 14:56:51 -0700429 mHorizontalAxis.setCount(columnCount);
Philip Milnef6679c82011-09-18 11:36:57 -0700430 invalidateStructure();
431 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100432 }
433
434 /**
435 * Returns whether or not this GridLayout will allocate default margins when no
436 * corresponding layout parameters are defined.
437 *
Philip Milne7fd94872011-06-07 20:14:17 -0700438 * @return {@code true} if default margins should be allocated
Philip Milne3f8956d2011-05-13 17:29:00 +0100439 *
440 * @see #setUseDefaultMargins(boolean)
441 *
442 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
443 */
444 public boolean getUseDefaultMargins() {
Adam Powell465ea742013-08-29 14:56:51 -0700445 return mUseDefaultMargins;
Philip Milne3f8956d2011-05-13 17:29:00 +0100446 }
447
448 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700449 * When {@code true}, GridLayout allocates default margins around children
Philip Milne3f8956d2011-05-13 17:29:00 +0100450 * based on the child's visual characteristics. Each of the
451 * margins so defined may be independently overridden by an assignment
452 * to the appropriate layout parameter.
453 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700454 * When {@code false}, the default value of all margins is zero.
Philip Milneaa616f32011-05-27 18:38:01 -0700455 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -0700456 * When setting to {@code true}, consider setting the value of the
Philip Milne1e548252011-06-16 19:02:33 -0700457 * {@link #setAlignmentMode(int) alignmentMode}
458 * property to {@link #ALIGN_BOUNDS}.
Philip Milne7fd94872011-06-07 20:14:17 -0700459 * <p>
460 * The default value of this property is {@code false}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100461 *
Philip Milne7fd94872011-06-07 20:14:17 -0700462 * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
Philip Milne3f8956d2011-05-13 17:29:00 +0100463 *
464 * @see #getUseDefaultMargins()
Philip Milne1e548252011-06-16 19:02:33 -0700465 * @see #setAlignmentMode(int)
Philip Milne3f8956d2011-05-13 17:29:00 +0100466 *
467 * @see MarginLayoutParams#leftMargin
468 * @see MarginLayoutParams#topMargin
469 * @see MarginLayoutParams#rightMargin
470 * @see MarginLayoutParams#bottomMargin
471 *
472 * @attr ref android.R.styleable#GridLayout_useDefaultMargins
473 */
474 public void setUseDefaultMargins(boolean useDefaultMargins) {
Adam Powell465ea742013-08-29 14:56:51 -0700475 this.mUseDefaultMargins = useDefaultMargins;
Philip Milneaa616f32011-05-27 18:38:01 -0700476 requestLayout();
477 }
478
479 /**
Philip Milne1e548252011-06-16 19:02:33 -0700480 * Returns the alignment mode.
Philip Milneaa616f32011-05-27 18:38:01 -0700481 *
Philip Milne1e548252011-06-16 19:02:33 -0700482 * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700483 *
Philip Milne1e548252011-06-16 19:02:33 -0700484 * @see #ALIGN_BOUNDS
485 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700486 *
Philip Milne1e548252011-06-16 19:02:33 -0700487 * @see #setAlignmentMode(int)
488 *
489 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700490 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700491 @AlignmentMode
Philip Milne1e548252011-06-16 19:02:33 -0700492 public int getAlignmentMode() {
Adam Powell465ea742013-08-29 14:56:51 -0700493 return mAlignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700494 }
495
496 /**
Philip Milne1e548252011-06-16 19:02:33 -0700497 * Sets the alignment mode to be used for all of the alignments between the
498 * children of this container.
Philip Milne7fd94872011-06-07 20:14:17 -0700499 * <p>
Philip Milne1e548252011-06-16 19:02:33 -0700500 * The default value of this property is {@link #ALIGN_MARGINS}.
Philip Milneaa616f32011-05-27 18:38:01 -0700501 *
Philip Milne1e548252011-06-16 19:02:33 -0700502 * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
Philip Milneaa616f32011-05-27 18:38:01 -0700503 *
Philip Milne1e548252011-06-16 19:02:33 -0700504 * @see #ALIGN_BOUNDS
505 * @see #ALIGN_MARGINS
Philip Milneaa616f32011-05-27 18:38:01 -0700506 *
Philip Milne1e548252011-06-16 19:02:33 -0700507 * @see #getAlignmentMode()
508 *
509 * @attr ref android.R.styleable#GridLayout_alignmentMode
Philip Milneaa616f32011-05-27 18:38:01 -0700510 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700511 public void setAlignmentMode(@AlignmentMode int alignmentMode) {
Adam Powell465ea742013-08-29 14:56:51 -0700512 this.mAlignmentMode = alignmentMode;
Philip Milneaa616f32011-05-27 18:38:01 -0700513 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100514 }
515
516 /**
517 * Returns whether or not row boundaries are ordered by their grid indices.
518 *
Philip Milne7fd94872011-06-07 20:14:17 -0700519 * @return {@code true} if row boundaries must appear in the order of their indices,
520 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100521 *
522 * @see #setRowOrderPreserved(boolean)
523 *
524 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
525 */
526 public boolean isRowOrderPreserved() {
Adam Powell465ea742013-08-29 14:56:51 -0700527 return mVerticalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100528 }
529
530 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700531 * When this property is {@code true}, GridLayout is forced to place the row boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700532 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100533 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700534 * When this property is {@code false} GridLayout is at liberty to place the vertical row
535 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700536 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700537 * The default value of this property is {@code true}.
Philip Milne7fd94872011-06-07 20:14:17 -0700538
539 * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
540 * of row boundaries
Philip Milne3f8956d2011-05-13 17:29:00 +0100541 *
542 * @see #isRowOrderPreserved()
543 *
544 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
545 */
546 public void setRowOrderPreserved(boolean rowOrderPreserved) {
Adam Powell465ea742013-08-29 14:56:51 -0700547 mVerticalAxis.setOrderPreserved(rowOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700548 invalidateStructure();
549 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100550 }
551
552 /**
553 * Returns whether or not column boundaries are ordered by their grid indices.
554 *
Philip Milne7fd94872011-06-07 20:14:17 -0700555 * @return {@code true} if column boundaries must appear in the order of their indices,
556 * {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +0100557 *
558 * @see #setColumnOrderPreserved(boolean)
559 *
560 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
561 */
562 public boolean isColumnOrderPreserved() {
Adam Powell465ea742013-08-29 14:56:51 -0700563 return mHorizontalAxis.isOrderPreserved();
Philip Milne3f8956d2011-05-13 17:29:00 +0100564 }
565
566 /**
Philip Milne7fd94872011-06-07 20:14:17 -0700567 * When this property is {@code true}, GridLayout is forced to place the column boundaries
Philip Milneaa616f32011-05-27 18:38:01 -0700568 * so that their associated grid indices are in ascending order in the view.
Philip Milne3f8956d2011-05-13 17:29:00 +0100569 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700570 * When this property is {@code false} GridLayout is at liberty to place the horizontal column
571 * boundaries in whatever order best fits the given constraints.
Philip Milne7fd94872011-06-07 20:14:17 -0700572 * <p>
Philip Milne899d5922011-07-21 11:39:37 -0700573 * The default value of this property is {@code true}.
Philip Milne3f8956d2011-05-13 17:29:00 +0100574 *
Philip Milne7fd94872011-06-07 20:14:17 -0700575 * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
Philip Milne3f8956d2011-05-13 17:29:00 +0100576 * of column boundaries.
577 *
578 * @see #isColumnOrderPreserved()
579 *
580 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
581 */
582 public void setColumnOrderPreserved(boolean columnOrderPreserved) {
Adam Powell465ea742013-08-29 14:56:51 -0700583 mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
Philip Milneaa616f32011-05-27 18:38:01 -0700584 invalidateStructure();
585 requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +0100586 }
587
Philip Milne211d0332012-04-24 14:45:14 -0700588 /**
589 * Return the printer that will log diagnostics from this layout.
590 *
591 * @see #setPrinter(android.util.Printer)
592 *
593 * @return the printer associated with this view
Adam Powell465ea742013-08-29 14:56:51 -0700594 *
595 * @hide
Philip Milne211d0332012-04-24 14:45:14 -0700596 */
597 public Printer getPrinter() {
Adam Powell465ea742013-08-29 14:56:51 -0700598 return mPrinter;
Philip Milne211d0332012-04-24 14:45:14 -0700599 }
600
601 /**
602 * Set the printer that will log diagnostics from this layout.
603 * The default value is created by {@link android.util.LogPrinter}.
604 *
605 * @param printer the printer associated with this layout
606 *
607 * @see #getPrinter()
Adam Powell465ea742013-08-29 14:56:51 -0700608 *
609 * @hide
Philip Milne211d0332012-04-24 14:45:14 -0700610 */
611 public void setPrinter(Printer printer) {
Adam Powell465ea742013-08-29 14:56:51 -0700612 this.mPrinter = (printer == null) ? NO_PRINTER : printer;
Philip Milne211d0332012-04-24 14:45:14 -0700613 }
614
Philip Milne5125e212011-07-21 11:39:37 -0700615 // Static utility methods
616
Philip Milnef6679c82011-09-18 11:36:57 -0700617 static int max2(int[] a, int valueIfEmpty) {
Philip Milne51f17d52011-06-13 10:44:49 -0700618 int result = valueIfEmpty;
619 for (int i = 0, N = a.length; i < N; i++) {
620 result = Math.max(result, a[i]);
621 }
622 return result;
623 }
624
Philip Milne899d5922011-07-21 11:39:37 -0700625 @SuppressWarnings("unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -0700626 static <T> T[] append(T[] a, T[] b) {
Philip Milne48b55242011-06-29 11:09:45 -0700627 T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
628 System.arraycopy(a, 0, result, 0, a.length);
629 System.arraycopy(b, 0, result, a.length, b.length);
Philip Milne3f8956d2011-05-13 17:29:00 +0100630 return result;
631 }
632
Philip Milnef6679c82011-09-18 11:36:57 -0700633 static Alignment getAlignment(int gravity, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -0700634 int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
635 int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
636 int flags = (gravity & mask) >> shift;
637 switch (flags) {
638 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
Philip Milne1557fd72012-04-04 23:41:34 -0700639 return horizontal ? LEFT : TOP;
Philip Milne5125e212011-07-21 11:39:37 -0700640 case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
Philip Milne1557fd72012-04-04 23:41:34 -0700641 return horizontal ? RIGHT : BOTTOM;
Philip Milne5125e212011-07-21 11:39:37 -0700642 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
643 return FILL;
644 case AXIS_SPECIFIED:
645 return CENTER;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800646 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION):
647 return START;
648 case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION):
649 return END;
Philip Milne5125e212011-07-21 11:39:37 -0700650 default:
651 return UNDEFINED_ALIGNMENT;
652 }
653 }
654
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700655 /** @noinspection UnusedParameters*/
Philip Milne1fd16372011-06-21 14:57:47 -0700656 private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
Philip Milne899d5922011-07-21 11:39:37 -0700657 if (c.getClass() == Space.class) {
658 return 0;
659 }
Adam Powell465ea742013-08-29 14:56:51 -0700660 return mDefaultGap / 2;
Philip Milne3f8956d2011-05-13 17:29:00 +0100661 }
662
Philip Milne1fd16372011-06-21 14:57:47 -0700663 private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
Philip Milne7b757812012-09-19 18:13:44 -0700664 return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100665 }
666
Philip Milne7a23b492012-04-24 22:12:36 -0700667 private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
Adam Powell465ea742013-08-29 14:56:51 -0700668 if (!mUseDefaultMargins) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100669 return 0;
670 }
Philip Milne93cd6a62011-07-12 14:49:45 -0700671 Spec spec = horizontal ? p.columnSpec : p.rowSpec;
Adam Powell465ea742013-08-29 14:56:51 -0700672 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne93cd6a62011-07-12 14:49:45 -0700673 Interval span = spec.span;
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800674 boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading;
675 boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
Philip Milne3f8956d2011-05-13 17:29:00 +0100676
Philip Milne1fd16372011-06-21 14:57:47 -0700677 return getDefaultMargin(c, isAtEdge, horizontal, leading);
Philip Milne3f8956d2011-05-13 17:29:00 +0100678 }
679
Philip Milnef6679c82011-09-18 11:36:57 -0700680 int getMargin1(View view, boolean horizontal, boolean leading) {
Philip Milne3f8956d2011-05-13 17:29:00 +0100681 LayoutParams lp = getLayoutParams(view);
682 int margin = horizontal ?
Philip Milneaa616f32011-05-27 18:38:01 -0700683 (leading ? lp.leftMargin : lp.rightMargin) :
684 (leading ? lp.topMargin : lp.bottomMargin);
Philip Milne7a23b492012-04-24 22:12:36 -0700685 return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin;
Philip Milne1fd16372011-06-21 14:57:47 -0700686 }
687
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700688 private int getMargin(View view, boolean horizontal, boolean leading) {
Adam Powell465ea742013-08-29 14:56:51 -0700689 if (mAlignmentMode == ALIGN_MARGINS) {
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700690 return getMargin1(view, horizontal, leading);
691 } else {
Adam Powell465ea742013-08-29 14:56:51 -0700692 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne4c8cf4c2011-08-03 11:50:50 -0700693 int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
694 LayoutParams lp = getLayoutParams(view);
695 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
696 int index = leading ? spec.span.min : spec.span.max;
697 return margins[index];
698 }
699 }
700
Philip Milne1fd16372011-06-21 14:57:47 -0700701 private int getTotalMargin(View child, boolean horizontal) {
702 return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
Philip Milne3f8956d2011-05-13 17:29:00 +0100703 }
704
Philip Milne899d5922011-07-21 11:39:37 -0700705 private static boolean fits(int[] a, int value, int start, int end) {
706 if (end > a.length) {
707 return false;
708 }
709 for (int i = start; i < end; i++) {
710 if (a[i] > value) {
711 return false;
712 }
713 }
714 return true;
715 }
716
717 private static void procrusteanFill(int[] a, int start, int end, int value) {
718 int length = a.length;
719 Arrays.fill(a, Math.min(start, length), Math.min(end, length), value);
720 }
721
722 private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) {
723 lp.setRowSpecSpan(new Interval(row, row + rowSpan));
724 lp.setColumnSpecSpan(new Interval(col, col + colSpan));
725 }
726
727 // Logic to avert infinite loops by ensuring that the cells can be placed somewhere.
728 private static int clip(Interval minorRange, boolean minorWasDefined, int count) {
729 int size = minorRange.size();
730 if (count == 0) {
731 return size;
732 }
733 int min = minorWasDefined ? min(minorRange.min, count) : 0;
734 return min(size, count - min);
735 }
736
Philip Milnef4748702011-06-09 18:30:32 -0700737 // install default indices for cells that don't define them
Philip Milne3f8956d2011-05-13 17:29:00 +0100738 private void validateLayoutParams() {
Adam Powell465ea742013-08-29 14:56:51 -0700739 final boolean horizontal = (mOrientation == HORIZONTAL);
740 final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Jim Miller782c04b2012-05-02 15:45:23 -0700741 final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
Philip Milne3f8956d2011-05-13 17:29:00 +0100742
Philip Milne899d5922011-07-21 11:39:37 -0700743 int major = 0;
744 int minor = 0;
745 int[] maxSizes = new int[count];
746
747 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milned7dd8902012-01-26 16:55:30 -0800748 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
Philip Milne899d5922011-07-21 11:39:37 -0700749
Philip Milnef6679c82011-09-18 11:36:57 -0700750 final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
751 final Interval majorRange = majorSpec.span;
752 final boolean majorWasDefined = majorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700753 final int majorSpan = majorRange.size();
754 if (majorWasDefined) {
755 major = majorRange.min;
Philip Milnef4748702011-06-09 18:30:32 -0700756 }
757
Philip Milnef6679c82011-09-18 11:36:57 -0700758 final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
759 final Interval minorRange = minorSpec.span;
760 final boolean minorWasDefined = minorSpec.startDefined;
Philip Milne899d5922011-07-21 11:39:37 -0700761 final int minorSpan = clip(minorRange, minorWasDefined, count);
762 if (minorWasDefined) {
763 minor = minorRange.min;
764 }
Philip Milnef4748702011-06-09 18:30:32 -0700765
Philip Milne899d5922011-07-21 11:39:37 -0700766 if (count != 0) {
767 // Find suitable row/col values when at least one is undefined.
768 if (!majorWasDefined || !minorWasDefined) {
769 while (!fits(maxSizes, major, minor, minor + minorSpan)) {
770 if (minorWasDefined) {
771 major++;
772 } else {
773 if (minor + minorSpan <= count) {
774 minor++;
775 } else {
776 minor = 0;
777 major++;
778 }
Philip Milnef4748702011-06-09 18:30:32 -0700779 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100780 }
781 }
Philip Milne899d5922011-07-21 11:39:37 -0700782 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan);
Philip Milne3f8956d2011-05-13 17:29:00 +0100783 }
Philip Milne899d5922011-07-21 11:39:37 -0700784
785 if (horizontal) {
786 setCellGroup(lp, major, majorSpan, minor, minorSpan);
787 } else {
788 setCellGroup(lp, minor, minorSpan, major, majorSpan);
789 }
790
791 minor = minor + minorSpan;
792 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100793 }
794
795 private void invalidateStructure() {
Adam Powell465ea742013-08-29 14:56:51 -0700796 mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
797 mHorizontalAxis.invalidateStructure();
798 mVerticalAxis.invalidateStructure();
Philip Milne899d5922011-07-21 11:39:37 -0700799 // This can end up being done twice. Better twice than not at all.
Philip Milne3f8956d2011-05-13 17:29:00 +0100800 invalidateValues();
801 }
802
803 private void invalidateValues() {
Philip Milneaa616f32011-05-27 18:38:01 -0700804 // Need null check because requestLayout() is called in View's initializer,
805 // before we are set up.
Adam Powell465ea742013-08-29 14:56:51 -0700806 if (mHorizontalAxis != null && mVerticalAxis != null) {
807 mHorizontalAxis.invalidateValues();
808 mVerticalAxis.invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -0700809 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100810 }
811
Philip Milned7dd8902012-01-26 16:55:30 -0800812 /** @hide */
813 @Override
814 protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) {
815 super.onSetLayoutParams(child, layoutParams);
Philip Milne0f57cea2012-05-12 09:34:25 -0700816
817 if (!checkLayoutParams(layoutParams)) {
818 handleInvalidParams("supplied LayoutParams are of the wrong type");
819 }
820
Philip Milned7dd8902012-01-26 16:55:30 -0800821 invalidateStructure();
Philip Milne3f8956d2011-05-13 17:29:00 +0100822 }
823
Philip Milnef6679c82011-09-18 11:36:57 -0700824 final LayoutParams getLayoutParams(View c) {
Philip Milned7dd8902012-01-26 16:55:30 -0800825 return (LayoutParams) c.getLayoutParams();
Philip Milne3f8956d2011-05-13 17:29:00 +0100826 }
827
Philip Milne0f57cea2012-05-12 09:34:25 -0700828 private static void handleInvalidParams(String msg) {
829 throw new IllegalArgumentException(msg + ". ");
830 }
831
832 private void checkLayoutParams(LayoutParams lp, boolean horizontal) {
833 String groupName = horizontal ? "column" : "row";
834 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
835 Interval span = spec.span;
836 if (span.min != UNDEFINED && span.min < 0) {
837 handleInvalidParams(groupName + " indices must be positive");
838 }
Adam Powell465ea742013-08-29 14:56:51 -0700839 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne0f57cea2012-05-12 09:34:25 -0700840 int count = axis.definedCount;
841 if (count != UNDEFINED) {
842 if (span.max > count) {
843 handleInvalidParams(groupName +
844 " indices (start + span) mustn't exceed the " + groupName + " count");
845 }
846 if (span.size() > count) {
847 handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count");
848 }
849 }
850 }
851
852 @Override
853 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
854 if (!(p instanceof LayoutParams)) {
855 return false;
856 }
857 LayoutParams lp = (LayoutParams) p;
858
859 checkLayoutParams(lp, true);
860 checkLayoutParams(lp, false);
861
862 return true;
863 }
864
Philip Milne3f8956d2011-05-13 17:29:00 +0100865 @Override
866 protected LayoutParams generateDefaultLayoutParams() {
867 return new LayoutParams();
868 }
869
870 @Override
871 public LayoutParams generateLayoutParams(AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -0700872 return new LayoutParams(getContext(), attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +0100873 }
874
875 @Override
876 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
877 return new LayoutParams(p);
878 }
879
880 // Draw grid
881
882 private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800883 if (isLayoutRtl()) {
884 int width = getWidth();
Philip Milne7b757812012-09-19 18:13:44 -0700885 graphics.drawLine(width - x1, y1, width - x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800886 } else {
Philip Milne7b757812012-09-19 18:13:44 -0700887 graphics.drawLine(x1, y1, x2, y2, paint);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -0800888 }
Philip Milne3f8956d2011-05-13 17:29:00 +0100889 }
890
Philip Milne10ca24a2012-04-23 15:38:27 -0700891 /**
892 * @hide
893 */
894 @Override
Philip Milne7b757812012-09-19 18:13:44 -0700895 protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700896 // Apply defaults, so as to remove UNDEFINED values
897 LayoutParams lp = new LayoutParams();
898 for (int i = 0; i < getChildCount(); i++) {
899 View c = getChildAt(i);
900 lp.setMargins(
Philip Milne7b757812012-09-19 18:13:44 -0700901 getMargin1(c, true, true),
902 getMargin1(c, false, true),
903 getMargin1(c, true, false),
904 getMargin1(c, false, false));
905 lp.onDebugDraw(c, canvas, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700906 }
Philip Milneb5599762011-08-05 11:04:36 -0700907 }
908
Philip Milne10ca24a2012-04-23 15:38:27 -0700909 /**
910 * @hide
911 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100912 @Override
Philip Milne10ca24a2012-04-23 15:38:27 -0700913 protected void onDebugDraw(Canvas canvas) {
Philip Milne10ca24a2012-04-23 15:38:27 -0700914 Paint paint = new Paint();
915 paint.setStyle(Paint.Style.STROKE);
916 paint.setColor(Color.argb(50, 255, 255, 255));
Philip Milne3f8956d2011-05-13 17:29:00 +0100917
Philip Milne7b757812012-09-19 18:13:44 -0700918 Insets insets = getOpticalInsets();
919
920 int top = getPaddingTop() + insets.top;
921 int left = getPaddingLeft() + insets.left;
922 int right = getWidth() - getPaddingRight() - insets.right;
923 int bottom = getHeight() - getPaddingBottom() - insets.bottom;
924
Adam Powell465ea742013-08-29 14:56:51 -0700925 int[] xs = mHorizontalAxis.locations;
Philip Milne10ca24a2012-04-23 15:38:27 -0700926 if (xs != null) {
927 for (int i = 0, length = xs.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700928 int x = left + xs[i];
929 drawLine(canvas, x, top, x, bottom, paint);
Philip Milne3f8956d2011-05-13 17:29:00 +0100930 }
931 }
Philip Milne10ca24a2012-04-23 15:38:27 -0700932
Adam Powell465ea742013-08-29 14:56:51 -0700933 int[] ys = mVerticalAxis.locations;
Philip Milne10ca24a2012-04-23 15:38:27 -0700934 if (ys != null) {
935 for (int i = 0, length = ys.length; i < length; i++) {
Philip Milne7b757812012-09-19 18:13:44 -0700936 int y = top + ys[i];
937 drawLine(canvas, left, y, right, y, paint);
Philip Milne10ca24a2012-04-23 15:38:27 -0700938 }
939 }
940
941 super.onDebugDraw(canvas);
Philip Milne3f8956d2011-05-13 17:29:00 +0100942 }
943
Philip Milne3f8956d2011-05-13 17:29:00 +0100944 // Add/remove
945
Philip Milnee7dda0b2011-07-19 10:35:56 -0700946 /**
947 * @hide
948 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100949 @Override
Philip Milnef51d91c2011-07-18 16:12:19 -0700950 protected void onViewAdded(View child) {
951 super.onViewAdded(child);
Philip Milne3f8956d2011-05-13 17:29:00 +0100952 invalidateStructure();
953 }
954
Philip Milnee7dda0b2011-07-19 10:35:56 -0700955 /**
956 * @hide
957 */
Philip Milne3f8956d2011-05-13 17:29:00 +0100958 @Override
Philip Milnef51d91c2011-07-18 16:12:19 -0700959 protected void onViewRemoved(View child) {
960 super.onViewRemoved(child);
Philip Milneb0ce49b2011-07-15 15:39:07 -0700961 invalidateStructure();
962 }
963
Philip Milne350f0a62011-07-19 14:00:56 -0700964 /**
965 * We need to call invalidateStructure() when a child's GONE flag changes state.
966 * This implementation is a catch-all, invalidating on any change in the visibility flags.
967 *
968 * @hide
969 */
970 @Override
Chet Haase0d299362012-01-26 10:51:48 -0800971 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
972 super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
973 if (oldVisibility == GONE || newVisibility == GONE) {
Philip Milnea8416442013-02-25 10:49:39 -0800974 invalidateStructure();
Chet Haase0d299362012-01-26 10:51:48 -0800975 }
Philip Milne350f0a62011-07-19 14:00:56 -0700976 }
977
Philip Milned7dd8902012-01-26 16:55:30 -0800978 private int computeLayoutParamsHashCode() {
979 int result = 1;
980 for (int i = 0, N = getChildCount(); i < N; i++) {
981 View c = getChildAt(i);
982 if (c.getVisibility() == View.GONE) continue;
983 LayoutParams lp = (LayoutParams) c.getLayoutParams();
984 result = 31 * result + lp.hashCode();
985 }
986 return result;
Philip Milneb3a8c542011-06-20 16:02:59 -0700987 }
988
Philip Milneedd69512012-03-14 17:21:33 -0700989 private void consistencyCheck() {
Adam Powell465ea742013-08-29 14:56:51 -0700990 if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) {
Philip Milneedd69512012-03-14 17:21:33 -0700991 validateLayoutParams();
Adam Powell465ea742013-08-29 14:56:51 -0700992 mLastLayoutParamsHashCode = computeLayoutParamsHashCode();
993 } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
994 mPrinter.println("The fields of some layout parameters were modified in between "
Philip Milne211d0332012-04-24 14:45:14 -0700995 + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
Philip Milneedd69512012-03-14 17:21:33 -0700996 invalidateStructure();
997 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -0800998 }
999 }
1000
1001 // Measurement
1002
Philip Milnee0b85cd2013-04-12 14:38:34 -07001003 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -07001004 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
Philip Milneedd69512012-03-14 17:21:33 -07001005 int childWidth, int childHeight) {
Philip Milne4a145d72011-09-29 14:14:25 -07001006 int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -07001007 getTotalMargin(child, true), childWidth);
Philip Milne4a145d72011-09-29 14:14:25 -07001008 int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
Philip Milnee0b85cd2013-04-12 14:38:34 -07001009 getTotalMargin(child, false), childHeight);
Philip Milne4a145d72011-09-29 14:14:25 -07001010 child.measure(childWidthSpec, childHeightSpec);
Philip Milneb3a8c542011-06-20 16:02:59 -07001011 }
1012
Philip Milnee0b85cd2013-04-12 14:38:34 -07001013 // Note: padding has already been removed from the supplied specs
Philip Milne4a145d72011-09-29 14:14:25 -07001014 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001015 for (int i = 0, N = getChildCount(); i < N; i++) {
1016 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001017 if (c.getVisibility() == View.GONE) continue;
Philip Milne4a145d72011-09-29 14:14:25 -07001018 LayoutParams lp = getLayoutParams(c);
1019 if (firstPass) {
1020 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
1021 } else {
Adam Powell465ea742013-08-29 14:56:51 -07001022 boolean horizontal = (mOrientation == HORIZONTAL);
Philip Milneecab1172011-10-25 15:07:19 -07001023 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -07001024 if (spec.alignment == FILL) {
1025 Interval span = spec.span;
Adam Powell465ea742013-08-29 14:56:51 -07001026 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Philip Milne4a145d72011-09-29 14:14:25 -07001027 int[] locations = axis.getLocations();
Philip Milneecab1172011-10-25 15:07:19 -07001028 int cellSize = locations[span.max] - locations[span.min];
1029 int viewSize = cellSize - getTotalMargin(c, horizontal);
1030 if (horizontal) {
1031 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);
Philip Milne4a145d72011-09-29 14:14:25 -07001032 } else {
Philip Milneecab1172011-10-25 15:07:19 -07001033 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);
Philip Milne4a145d72011-09-29 14:14:25 -07001034 }
1035 }
1036 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001037 }
1038 }
1039
Philip Milnee0b85cd2013-04-12 14:38:34 -07001040 static int adjust(int measureSpec, int delta) {
1041 return makeMeasureSpec(
1042 MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec));
1043 }
1044
Philip Milne3f8956d2011-05-13 17:29:00 +01001045 @Override
1046 protected void onMeasure(int widthSpec, int heightSpec) {
Philip Milneedd69512012-03-14 17:21:33 -07001047 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001048
Philip Milne4a145d72011-09-29 14:14:25 -07001049 /** If we have been called by {@link View#measure(int, int)}, one of width or height
1050 * is likely to have changed. We must invalidate if so. */
1051 invalidateValues();
Philip Milne3f8956d2011-05-13 17:29:00 +01001052
Philip Milnee0b85cd2013-04-12 14:38:34 -07001053 int hPadding = getPaddingLeft() + getPaddingRight();
1054 int vPadding = getPaddingTop() + getPaddingBottom();
Philip Milne3f8956d2011-05-13 17:29:00 +01001055
Philip Milnee0b85cd2013-04-12 14:38:34 -07001056 int widthSpecSansPadding = adjust( widthSpec, -hPadding);
1057 int heightSpecSansPadding = adjust(heightSpec, -vPadding);
1058
1059 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);
1060
1061 int widthSansPadding;
1062 int heightSansPadding;
Philip Milne4a145d72011-09-29 14:14:25 -07001063
1064 // Use the orientation property to decide which axis should be laid out first.
Adam Powell465ea742013-08-29 14:56:51 -07001065 if (mOrientation == HORIZONTAL) {
1066 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
Philip Milnee0b85cd2013-04-12 14:38:34 -07001067 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
Adam Powell465ea742013-08-29 14:56:51 -07001068 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001069 } else {
Adam Powell465ea742013-08-29 14:56:51 -07001070 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
Philip Milnee0b85cd2013-04-12 14:38:34 -07001071 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
Adam Powell465ea742013-08-29 14:56:51 -07001072 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
Philip Milne4a145d72011-09-29 14:14:25 -07001073 }
1074
Philip Milnee0b85cd2013-04-12 14:38:34 -07001075 int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth());
1076 int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight());
Philip Milne09e2d4d2011-06-20 10:28:18 -07001077
Philip Milne3f8956d2011-05-13 17:29:00 +01001078 setMeasuredDimension(
Philip Milnee0b85cd2013-04-12 14:38:34 -07001079 resolveSizeAndState(measuredWidth, widthSpec, 0),
Philip Milne09e2d4d2011-06-20 10:28:18 -07001080 resolveSizeAndState(measuredHeight, heightSpec, 0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001081 }
1082
Philip Milne48b55242011-06-29 11:09:45 -07001083 private int getMeasurement(View c, boolean horizontal) {
Philip Milne7b757812012-09-19 18:13:44 -07001084 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
Philip Milneaa616f32011-05-27 18:38:01 -07001085 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001086
Philip Milnef6679c82011-09-18 11:36:57 -07001087 final int getMeasurementIncludingMargin(View c, boolean horizontal) {
Philip Milned7dd8902012-01-26 16:55:30 -08001088 if (c.getVisibility() == View.GONE) {
Philip Milne5125e212011-07-21 11:39:37 -07001089 return 0;
1090 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001091 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
Philip Milne3f8956d2011-05-13 17:29:00 +01001092 }
1093
Philip Milne3f8956d2011-05-13 17:29:00 +01001094 @Override
Philip Milneaa616f32011-05-27 18:38:01 -07001095 public void requestLayout() {
1096 super.requestLayout();
Philip Milne3f8956d2011-05-13 17:29:00 +01001097 invalidateValues();
Philip Milneaa616f32011-05-27 18:38:01 -07001098 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001099
Philip Milnef6679c82011-09-18 11:36:57 -07001100 final Alignment getAlignment(Alignment alignment, boolean horizontal) {
Philip Milne5125e212011-07-21 11:39:37 -07001101 return (alignment != UNDEFINED_ALIGNMENT) ? alignment :
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08001102 (horizontal ? START : BASELINE);
Philip Milne5125e212011-07-21 11:39:37 -07001103 }
1104
Philip Milneaa616f32011-05-27 18:38:01 -07001105 // Layout container
1106
1107 /**
1108 * {@inheritDoc}
1109 */
1110 /*
1111 The layout operation is implemented by delegating the heavy lifting to the
1112 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
1113 Together they compute the locations of the vertical and horizontal lines of
1114 the grid (respectively!).
1115
1116 This method is then left with the simpler task of applying margins, gravity
1117 and sizing to each child view and then placing it in its cell.
1118 */
1119 @Override
Philip Milne09e2d4d2011-06-20 10:28:18 -07001120 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Philip Milneedd69512012-03-14 17:21:33 -07001121 consistencyCheck();
Philip Milned7dd8902012-01-26 16:55:30 -08001122
Philip Milne09e2d4d2011-06-20 10:28:18 -07001123 int targetWidth = right - left;
1124 int targetHeight = bottom - top;
Philip Milne3f8956d2011-05-13 17:29:00 +01001125
1126 int paddingLeft = getPaddingLeft();
1127 int paddingTop = getPaddingTop();
1128 int paddingRight = getPaddingRight();
1129 int paddingBottom = getPaddingBottom();
1130
Adam Powell465ea742013-08-29 14:56:51 -07001131 mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
1132 mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
Philip Milne3f8956d2011-05-13 17:29:00 +01001133
Adam Powell465ea742013-08-29 14:56:51 -07001134 int[] hLocations = mHorizontalAxis.getLocations();
1135 int[] vLocations = mVerticalAxis.getLocations();
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001136
Philip Milneb3a8c542011-06-20 16:02:59 -07001137 for (int i = 0, N = getChildCount(); i < N; i++) {
1138 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001139 if (c.getVisibility() == View.GONE) continue;
Philip Milneb3a8c542011-06-20 16:02:59 -07001140 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001141 Spec columnSpec = lp.columnSpec;
1142 Spec rowSpec = lp.rowSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001143
Philip Milne93cd6a62011-07-12 14:49:45 -07001144 Interval colSpan = columnSpec.span;
1145 Interval rowSpan = rowSpec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001146
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001147 int x1 = hLocations[colSpan.min];
1148 int y1 = vLocations[rowSpan.min];
Philip Milneaa616f32011-05-27 18:38:01 -07001149
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001150 int x2 = hLocations[colSpan.max];
1151 int y2 = vLocations[rowSpan.max];
Philip Milne3f8956d2011-05-13 17:29:00 +01001152
1153 int cellWidth = x2 - x1;
1154 int cellHeight = y2 - y1;
1155
Philip Milne48b55242011-06-29 11:09:45 -07001156 int pWidth = getMeasurement(c, true);
1157 int pHeight = getMeasurement(c, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001158
Philip Milne5125e212011-07-21 11:39:37 -07001159 Alignment hAlign = getAlignment(columnSpec.alignment, true);
1160 Alignment vAlign = getAlignment(rowSpec.alignment, false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001161
Adam Powell465ea742013-08-29 14:56:51 -07001162 Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i);
1163 Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i);
Philip Milne7fd94872011-06-07 20:14:17 -07001164
1165 // Gravity offsets: the location of the alignment group relative to its cell group.
Philip Milne6216e872012-02-16 17:15:50 -08001166 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true));
1167 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true));
Philip Milne7fd94872011-06-07 20:14:17 -07001168
Philip Milne6216e872012-02-16 17:15:50 -08001169 int leftMargin = getMargin(c, true, true);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001170 int topMargin = getMargin(c, false, true);
Philip Milne6216e872012-02-16 17:15:50 -08001171 int rightMargin = getMargin(c, true, false);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001172 int bottomMargin = getMargin(c, false, false);
Philip Milne7fd94872011-06-07 20:14:17 -07001173
Philip Milne1557fd72012-04-04 23:41:34 -07001174 int sumMarginsX = leftMargin + rightMargin;
1175 int sumMarginsY = topMargin + bottomMargin;
Philip Milne7fd94872011-06-07 20:14:17 -07001176
Philip Milne1557fd72012-04-04 23:41:34 -07001177 // Alignment offsets: the location of the view relative to its alignment group.
1178 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
1179 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
1180
1181 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
1182 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
Philip Milne7fd94872011-06-07 20:14:17 -07001183
Philip Milne6216e872012-02-16 17:15:50 -08001184 int dx = x1 + gravityOffsetX + alignmentOffsetX;
Philip Milne3f8956d2011-05-13 17:29:00 +01001185
Philip Milne6216e872012-02-16 17:15:50 -08001186 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx :
1187 targetWidth - width - paddingRight - rightMargin - dx;
1188 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
Philip Milne3f8956d2011-05-13 17:29:00 +01001189
Philip Milne899d5922011-07-21 11:39:37 -07001190 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
1191 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
1192 }
Philip Milneb3a8c542011-06-20 16:02:59 -07001193 c.layout(cx, cy, cx + width, cy + height);
Philip Milne3f8956d2011-05-13 17:29:00 +01001194 }
1195 }
1196
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001197 @Override
1198 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1199 super.onInitializeAccessibilityEvent(event);
1200 event.setClassName(GridLayout.class.getName());
1201 }
1202
1203 @Override
1204 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1205 super.onInitializeAccessibilityNodeInfo(info);
1206 info.setClassName(GridLayout.class.getName());
1207 }
1208
Philip Milne3f8956d2011-05-13 17:29:00 +01001209 // Inner classes
1210
Philip Milneaa616f32011-05-27 18:38:01 -07001211 /*
1212 This internal class houses the algorithm for computing the locations of grid lines;
1213 along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
1214 distinguished by the "horizontal" flag which is true for the horizontal axis and false
1215 for the vertical one.
1216 */
Philip Milnef6679c82011-09-18 11:36:57 -07001217 final class Axis {
Philip Milne48b55242011-06-29 11:09:45 -07001218 private static final int NEW = 0;
Philip Milne3f8956d2011-05-13 17:29:00 +01001219 private static final int PENDING = 1;
1220 private static final int COMPLETE = 2;
1221
1222 public final boolean horizontal;
1223
Philip Milnef6679c82011-09-18 11:36:57 -07001224 public int definedCount = UNDEFINED;
Philip Milne4a145d72011-09-29 14:14:25 -07001225 private int maxIndex = UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001226
Philip Milne93cd6a62011-07-12 14:49:45 -07001227 PackedMap<Spec, Bounds> groupBounds;
Philip Milne3f8956d2011-05-13 17:29:00 +01001228 public boolean groupBoundsValid = false;
1229
Philip Milne48b55242011-06-29 11:09:45 -07001230 PackedMap<Interval, MutableInt> forwardLinks;
1231 public boolean forwardLinksValid = false;
1232
1233 PackedMap<Interval, MutableInt> backwardLinks;
1234 public boolean backwardLinksValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001235
Philip Milne3f8956d2011-05-13 17:29:00 +01001236 public int[] leadingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001237 public boolean leadingMarginsValid = false;
1238
Philip Milne3f8956d2011-05-13 17:29:00 +01001239 public int[] trailingMargins;
Philip Milneaa616f32011-05-27 18:38:01 -07001240 public boolean trailingMarginsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001241
1242 public Arc[] arcs;
1243 public boolean arcsValid = false;
1244
Philip Milneaa616f32011-05-27 18:38:01 -07001245 public int[] locations;
Philip Milne48b55242011-06-29 11:09:45 -07001246 public boolean locationsValid = false;
Philip Milneaa616f32011-05-27 18:38:01 -07001247
Philip Milnef6679c82011-09-18 11:36:57 -07001248 boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001249
Philip Milne48b55242011-06-29 11:09:45 -07001250 private MutableInt parentMin = new MutableInt(0);
1251 private MutableInt parentMax = new MutableInt(-MAX_SIZE);
1252
Philip Milne3f8956d2011-05-13 17:29:00 +01001253 private Axis(boolean horizontal) {
1254 this.horizontal = horizontal;
1255 }
1256
Philip Milne4a145d72011-09-29 14:14:25 -07001257 private int calculateMaxIndex() {
1258 // the number Integer.MIN_VALUE + 1 comes up in undefined cells
1259 int result = -1;
Philip Milneb3a8c542011-06-20 16:02:59 -07001260 for (int i = 0, N = getChildCount(); i < N; i++) {
1261 View c = getChildAt(i);
Philip Milneb3a8c542011-06-20 16:02:59 -07001262 LayoutParams params = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001263 Spec spec = horizontal ? params.columnSpec : params.rowSpec;
Philip Milne4a145d72011-09-29 14:14:25 -07001264 Interval span = spec.span;
1265 result = max(result, span.min);
1266 result = max(result, span.max);
Philip Milne0f57cea2012-05-12 09:34:25 -07001267 result = max(result, span.size());
Philip Milne3f8956d2011-05-13 17:29:00 +01001268 }
Philip Milne4a145d72011-09-29 14:14:25 -07001269 return result == -1 ? UNDEFINED : result;
Philip Milne3f8956d2011-05-13 17:29:00 +01001270 }
1271
Philip Milne4a145d72011-09-29 14:14:25 -07001272 private int getMaxIndex() {
1273 if (maxIndex == UNDEFINED) {
1274 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children
Philip Milne3f8956d2011-05-13 17:29:00 +01001275 }
Philip Milne4a145d72011-09-29 14:14:25 -07001276 return maxIndex;
Philip Milnef6679c82011-09-18 11:36:57 -07001277 }
1278
1279 public int getCount() {
Philip Milne4a145d72011-09-29 14:14:25 -07001280 return max(definedCount, getMaxIndex());
Philip Milne3f8956d2011-05-13 17:29:00 +01001281 }
1282
1283 public void setCount(int count) {
Philip Milne0f57cea2012-05-12 09:34:25 -07001284 if (count != UNDEFINED && count < getMaxIndex()) {
1285 handleInvalidParams((horizontal ? "column" : "row") +
1286 "Count must be greater than or equal to the maximum of all grid indices " +
1287 "(and spans) defined in the LayoutParams of each child");
1288 }
Philip Milnef6679c82011-09-18 11:36:57 -07001289 this.definedCount = count;
Philip Milne3f8956d2011-05-13 17:29:00 +01001290 }
1291
1292 public boolean isOrderPreserved() {
Philip Milnef6679c82011-09-18 11:36:57 -07001293 return orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001294 }
1295
1296 public void setOrderPreserved(boolean orderPreserved) {
Philip Milnef6679c82011-09-18 11:36:57 -07001297 this.orderPreserved = orderPreserved;
Philip Milne3f8956d2011-05-13 17:29:00 +01001298 invalidateStructure();
1299 }
1300
Philip Milne93cd6a62011-07-12 14:49:45 -07001301 private PackedMap<Spec, Bounds> createGroupBounds() {
1302 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class);
Philip Milne48b55242011-06-29 11:09:45 -07001303 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milneb3a8c542011-06-20 16:02:59 -07001304 View c = getChildAt(i);
Philip Milnea8416442013-02-25 10:49:39 -08001305 // we must include views that are GONE here, see introductory javadoc
Philip Milne5125e212011-07-21 11:39:37 -07001306 LayoutParams lp = getLayoutParams(c);
1307 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1308 Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds();
1309 assoc.put(spec, bounds);
Philip Milne3f8956d2011-05-13 17:29:00 +01001310 }
Philip Milne48b55242011-06-29 11:09:45 -07001311 return assoc.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001312 }
1313
1314 private void computeGroupBounds() {
Philip Milneb3a8c542011-06-20 16:02:59 -07001315 Bounds[] values = groupBounds.values;
1316 for (int i = 0; i < values.length; i++) {
1317 values[i].reset();
Philip Milne3f8956d2011-05-13 17:29:00 +01001318 }
Philip Milneaa616f32011-05-27 18:38:01 -07001319 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001320 View c = getChildAt(i);
Philip Milnea8416442013-02-25 10:49:39 -08001321 // we must include views that are GONE here, see introductory javadoc
Philip Milne3f8956d2011-05-13 17:29:00 +01001322 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001323 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Philip Milne1557fd72012-04-04 23:41:34 -07001324 groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
Philip Milne3f8956d2011-05-13 17:29:00 +01001325 }
1326 }
1327
Philip Milnef6679c82011-09-18 11:36:57 -07001328 public PackedMap<Spec, Bounds> getGroupBounds() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001329 if (groupBounds == null) {
1330 groupBounds = createGroupBounds();
1331 }
1332 if (!groupBoundsValid) {
1333 computeGroupBounds();
1334 groupBoundsValid = true;
1335 }
1336 return groupBounds;
1337 }
1338
1339 // Add values computed by alignment - taking the max of all alignments in each span
Philip Milne48b55242011-06-29 11:09:45 -07001340 private PackedMap<Interval, MutableInt> createLinks(boolean min) {
1341 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class);
Philip Milne93cd6a62011-07-12 14:49:45 -07001342 Spec[] keys = getGroupBounds().keys;
Philip Milne48b55242011-06-29 11:09:45 -07001343 for (int i = 0, N = keys.length; i < N; i++) {
1344 Interval span = min ? keys[i].span : keys[i].span.inverse();
1345 result.put(span, new MutableInt());
Philip Milne3f8956d2011-05-13 17:29:00 +01001346 }
Philip Milne48b55242011-06-29 11:09:45 -07001347 return result.pack();
Philip Milne3f8956d2011-05-13 17:29:00 +01001348 }
1349
Philip Milne48b55242011-06-29 11:09:45 -07001350 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) {
1351 MutableInt[] spans = links.values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001352 for (int i = 0; i < spans.length; i++) {
1353 spans[i].reset();
1354 }
1355
Philip Milne5d1a9842011-07-07 11:47:08 -07001356 // Use getter to trigger a re-evaluation
Philip Milne48b55242011-06-29 11:09:45 -07001357 Bounds[] bounds = getGroupBounds().values;
Philip Milne3f8956d2011-05-13 17:29:00 +01001358 for (int i = 0; i < bounds.length; i++) {
Philip Milne48b55242011-06-29 11:09:45 -07001359 int size = bounds[i].size(min);
Philip Milne48b55242011-06-29 11:09:45 -07001360 MutableInt valueHolder = links.getValue(i);
Philip Milne5125e212011-07-21 11:39:37 -07001361 // this effectively takes the max() of the minima and the min() of the maxima
1362 valueHolder.value = max(valueHolder.value, min ? size : -size);
Philip Milne3f8956d2011-05-13 17:29:00 +01001363 }
1364 }
1365
Philip Milne48b55242011-06-29 11:09:45 -07001366 private PackedMap<Interval, MutableInt> getForwardLinks() {
1367 if (forwardLinks == null) {
1368 forwardLinks = createLinks(true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001369 }
Philip Milne48b55242011-06-29 11:09:45 -07001370 if (!forwardLinksValid) {
1371 computeLinks(forwardLinks, true);
1372 forwardLinksValid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01001373 }
Philip Milne48b55242011-06-29 11:09:45 -07001374 return forwardLinks;
Philip Milne3f8956d2011-05-13 17:29:00 +01001375 }
1376
Philip Milne48b55242011-06-29 11:09:45 -07001377 private PackedMap<Interval, MutableInt> getBackwardLinks() {
1378 if (backwardLinks == null) {
1379 backwardLinks = createLinks(false);
1380 }
1381 if (!backwardLinksValid) {
1382 computeLinks(backwardLinks, false);
1383 backwardLinksValid = true;
1384 }
1385 return backwardLinks;
1386 }
1387
1388 private void include(List<Arc> arcs, Interval key, MutableInt size,
Philip Milneedd69512012-03-14 17:21:33 -07001389 boolean ignoreIfAlreadyPresent) {
Philip Milne48b55242011-06-29 11:09:45 -07001390 /*
1391 Remove self referential links.
1392 These appear:
1393 . as parental constraints when GridLayout has no children
1394 . when components have been marked as GONE
1395 */
1396 if (key.size() == 0) {
1397 return;
1398 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001399 // this bit below should really be computed outside here -
Philip Milne48b55242011-06-29 11:09:45 -07001400 // its just to stop default (row/col > 0) constraints obliterating valid entries
1401 if (ignoreIfAlreadyPresent) {
1402 for (Arc arc : arcs) {
1403 Interval span = arc.span;
1404 if (span.equals(key)) {
1405 return;
1406 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001407 }
1408 }
1409 arcs.add(new Arc(key, size));
1410 }
1411
Philip Milne48b55242011-06-29 11:09:45 -07001412 private void include(List<Arc> arcs, Interval key, MutableInt size) {
1413 include(arcs, key, size, true);
Philip Milne3f8956d2011-05-13 17:29:00 +01001414 }
1415
Philip Milneaa616f32011-05-27 18:38:01 -07001416 // Group arcs by their first vertex, returning an array of arrays.
Philip Milne3f8956d2011-05-13 17:29:00 +01001417 // This is linear in the number of arcs.
Philip Milnef6679c82011-09-18 11:36:57 -07001418 Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
Philip Milne48b55242011-06-29 11:09:45 -07001419 int N = getCount() + 1; // the number of vertices
Philip Milne3f8956d2011-05-13 17:29:00 +01001420 Arc[][] result = new Arc[N][];
1421 int[] sizes = new int[N];
1422 for (Arc arc : arcs) {
1423 sizes[arc.span.min]++;
Philip Milne5d1a9842011-07-07 11:47:08 -07001424 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001425 for (int i = 0; i < sizes.length; i++) {
1426 result[i] = new Arc[sizes[i]];
1427 }
1428 // reuse the sizes array to hold the current last elements as we insert each arc
1429 Arrays.fill(sizes, 0);
1430 for (Arc arc : arcs) {
1431 int i = arc.span.min;
1432 result[i][sizes[i]++] = arc;
1433 }
1434
1435 return result;
1436 }
1437
Philip Milne48b55242011-06-29 11:09:45 -07001438 private Arc[] topologicalSort(final Arc[] arcs) {
1439 return new Object() {
1440 Arc[] result = new Arc[arcs.length];
1441 int cursor = result.length - 1;
1442 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs);
Philip Milne3f8956d2011-05-13 17:29:00 +01001443 int[] visited = new int[getCount() + 1];
1444
Philip Milne48b55242011-06-29 11:09:45 -07001445 void walk(int loc) {
1446 switch (visited[loc]) {
1447 case NEW: {
1448 visited[loc] = PENDING;
1449 for (Arc arc : arcsByVertex[loc]) {
1450 walk(arc.span.max);
1451 result[cursor--] = arc;
Philip Milne3f8956d2011-05-13 17:29:00 +01001452 }
Philip Milne48b55242011-06-29 11:09:45 -07001453 visited[loc] = COMPLETE;
1454 break;
Philip Milne3f8956d2011-05-13 17:29:00 +01001455 }
Philip Milne48b55242011-06-29 11:09:45 -07001456 case PENDING: {
Philip Milneb65408f2012-05-21 10:44:46 -07001457 // le singe est dans l'arbre
Philip Milne48b55242011-06-29 11:09:45 -07001458 assert false;
1459 break;
1460 }
1461 case COMPLETE: {
1462 break;
1463 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001464 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001465 }
Philip Milne48b55242011-06-29 11:09:45 -07001466
1467 Arc[] sort() {
1468 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) {
1469 walk(loc);
1470 }
1471 assert cursor == -1;
1472 return result;
1473 }
1474 }.sort();
1475 }
1476
1477 private Arc[] topologicalSort(List<Arc> arcs) {
1478 return topologicalSort(arcs.toArray(new Arc[arcs.size()]));
Philip Milne3f8956d2011-05-13 17:29:00 +01001479 }
1480
Philip Milne48b55242011-06-29 11:09:45 -07001481 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) {
1482 for (int i = 0; i < links.keys.length; i++) {
1483 Interval key = links.keys[i];
1484 include(result, key, links.values[i], false);
Philip Milne3f8956d2011-05-13 17:29:00 +01001485 }
Philip Milne48b55242011-06-29 11:09:45 -07001486 }
1487
1488 private Arc[] createArcs() {
1489 List<Arc> mins = new ArrayList<Arc>();
1490 List<Arc> maxs = new ArrayList<Arc>();
1491
1492 // Add the minimum values from the components.
1493 addComponentSizes(mins, getForwardLinks());
1494 // Add the maximum values from the components.
1495 addComponentSizes(maxs, getBackwardLinks());
Philip Milne3f8956d2011-05-13 17:29:00 +01001496
Philip Milne48b55242011-06-29 11:09:45 -07001497 // Add ordering constraints to prevent row/col sizes from going negative
Philip Milnef6679c82011-09-18 11:36:57 -07001498 if (orderPreserved) {
Philip Milne48b55242011-06-29 11:09:45 -07001499 // Add a constraint for every row/col
Philip Milne3f8956d2011-05-13 17:29:00 +01001500 for (int i = 0; i < getCount(); i++) {
Philip Milne899d5922011-07-21 11:39:37 -07001501 include(mins, new Interval(i, i + 1), new MutableInt(0));
Philip Milne3f8956d2011-05-13 17:29:00 +01001502 }
1503 }
Philip Milne48b55242011-06-29 11:09:45 -07001504
1505 // Add the container constraints. Use the version of include that allows
1506 // duplicate entries in case a child spans the entire grid.
1507 int N = getCount();
1508 include(mins, new Interval(0, N), parentMin, false);
1509 include(maxs, new Interval(N, 0), parentMax, false);
1510
1511 // Sort
1512 Arc[] sMins = topologicalSort(mins);
1513 Arc[] sMaxs = topologicalSort(maxs);
1514
1515 return append(sMins, sMaxs);
1516 }
1517
1518 private void computeArcs() {
1519 // getting the links validates the values that are shared by the arc list
1520 getForwardLinks();
1521 getBackwardLinks();
Philip Milne3f8956d2011-05-13 17:29:00 +01001522 }
1523
Philip Milneaa616f32011-05-27 18:38:01 -07001524 public Arc[] getArcs() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001525 if (arcs == null) {
Philip Milneaa616f32011-05-27 18:38:01 -07001526 arcs = createArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001527 }
1528 if (!arcsValid) {
Philip Milne48b55242011-06-29 11:09:45 -07001529 computeArcs();
Philip Milne3f8956d2011-05-13 17:29:00 +01001530 arcsValid = true;
1531 }
1532 return arcs;
1533 }
1534
Philip Milneaa616f32011-05-27 18:38:01 -07001535 private boolean relax(int[] locations, Arc entry) {
Philip Milne48b55242011-06-29 11:09:45 -07001536 if (!entry.valid) {
1537 return false;
1538 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001539 Interval span = entry.span;
1540 int u = span.min;
1541 int v = span.max;
1542 int value = entry.value.value;
1543 int candidate = locations[u] + value;
Philip Milneaa616f32011-05-27 18:38:01 -07001544 if (candidate > locations[v]) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001545 locations[v] = candidate;
1546 return true;
1547 }
1548 return false;
1549 }
1550
Philip Milnef6679c82011-09-18 11:36:57 -07001551 private void init(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001552 Arrays.fill(locations, 0);
Philip Milnef6679c82011-09-18 11:36:57 -07001553 }
1554
1555 private String arcsToString(List<Arc> arcs) {
Philip Milne4a145d72011-09-29 14:14:25 -07001556 String var = horizontal ? "x" : "y";
Philip Milnef6679c82011-09-18 11:36:57 -07001557 StringBuilder result = new StringBuilder();
Philip Milne4a145d72011-09-29 14:14:25 -07001558 boolean first = true;
1559 for (Arc arc : arcs) {
1560 if (first) {
1561 first = false;
Philip Milnef6679c82011-09-18 11:36:57 -07001562 } else {
Philip Milne4a145d72011-09-29 14:14:25 -07001563 result = result.append(", ");
Philip Milnef6679c82011-09-18 11:36:57 -07001564 }
1565 int src = arc.span.min;
1566 int dst = arc.span.max;
1567 int value = arc.value.value;
1568 result.append((src < dst) ?
Philip Milneedd69512012-03-14 17:21:33 -07001569 var + dst + "-" + var + src + ">=" + value :
1570 var + src + "-" + var + dst + "<=" + -value);
Philip Milnef6679c82011-09-18 11:36:57 -07001571
1572 }
1573 return result.toString();
1574 }
1575
1576 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) {
1577 List<Arc> culprits = new ArrayList<Arc>();
1578 List<Arc> removed = new ArrayList<Arc>();
1579 for (int c = 0; c < arcs.length; c++) {
1580 Arc arc = arcs[c];
1581 if (culprits0[c]) {
1582 culprits.add(arc);
1583 }
1584 if (!arc.valid) {
1585 removed.add(arc);
1586 }
1587 }
Adam Powell465ea742013-08-29 14:56:51 -07001588 mPrinter.println(axisName + " constraints: " + arcsToString(culprits) +
Philip Milne211d0332012-04-24 14:45:14 -07001589 " are inconsistent; permanently removing: " + arcsToString(removed) + ". ");
Philip Milnef6679c82011-09-18 11:36:57 -07001590 }
1591
Philip Milneaa616f32011-05-27 18:38:01 -07001592 /*
1593 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
1594
1595 GridLayout converts its requirements into a system of linear constraints of the
1596 form:
1597
1598 x[i] - x[j] < a[k]
1599
1600 Where the x[i] are variables and the a[k] are constants.
1601
1602 For example, if the variables were instead labeled x, y, z we might have:
1603
1604 x - y < 17
1605 y - z < 23
1606 z - x < 42
1607
1608 This is a special case of the Linear Programming problem that is, in turn,
1609 equivalent to the single-source shortest paths problem on a digraph, for
1610 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
Philip Milneaa616f32011-05-27 18:38:01 -07001611 */
Philip Milne48b55242011-06-29 11:09:45 -07001612 private void solve(Arc[] arcs, int[] locations) {
Philip Milnef6679c82011-09-18 11:36:57 -07001613 String axisName = horizontal ? "horizontal" : "vertical";
Philip Milne3f8956d2011-05-13 17:29:00 +01001614 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
Philip Milnef6679c82011-09-18 11:36:57 -07001615 boolean[] originalCulprits = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001616
Philip Milnef6679c82011-09-18 11:36:57 -07001617 for (int p = 0; p < arcs.length; p++) {
1618 init(locations);
1619
1620 // We take one extra pass over traditional Bellman-Ford (and omit their final step)
1621 for (int i = 0; i < N; i++) {
1622 boolean changed = false;
1623 for (int j = 0, length = arcs.length; j < length; j++) {
1624 changed |= relax(locations, arcs[j]);
Philip Milne3f8956d2011-05-13 17:29:00 +01001625 }
Philip Milnef6679c82011-09-18 11:36:57 -07001626 if (!changed) {
1627 if (originalCulprits != null) {
1628 logError(axisName, arcs, originalCulprits);
1629 }
Philip Milnef6679c82011-09-18 11:36:57 -07001630 return;
Philip Milne48b55242011-06-29 11:09:45 -07001631 }
Philip Milnef6679c82011-09-18 11:36:57 -07001632 }
1633
1634 boolean[] culprits = new boolean[arcs.length];
1635 for (int i = 0; i < N; i++) {
1636 for (int j = 0, length = arcs.length; j < length; j++) {
1637 culprits[j] |= relax(locations, arcs[j]);
1638 }
1639 }
1640
1641 if (p == 0) {
1642 originalCulprits = culprits;
1643 }
1644
1645 for (int i = 0; i < arcs.length; i++) {
1646 if (culprits[i]) {
1647 Arc arc = arcs[i];
1648 // Only remove max values, min values alone cannot be inconsistent
1649 if (arc.span.min < arc.span.max) {
1650 continue;
1651 }
1652 arc.valid = false;
1653 break;
1654 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001655 }
1656 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001657 }
1658
Philip Milneaa616f32011-05-27 18:38:01 -07001659 private void computeMargins(boolean leading) {
1660 int[] margins = leading ? leadingMargins : trailingMargins;
Philip Milneb3a8c542011-06-20 16:02:59 -07001661 for (int i = 0, N = getChildCount(); i < N; i++) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001662 View c = getChildAt(i);
Philip Milned7dd8902012-01-26 16:55:30 -08001663 if (c.getVisibility() == View.GONE) continue;
Philip Milne3f8956d2011-05-13 17:29:00 +01001664 LayoutParams lp = getLayoutParams(c);
Philip Milne93cd6a62011-07-12 14:49:45 -07001665 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
1666 Interval span = spec.span;
Philip Milne3f8956d2011-05-13 17:29:00 +01001667 int index = leading ? span.min : span.max;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07001668 margins[index] = max(margins[index], getMargin1(c, horizontal, leading));
Philip Milne3f8956d2011-05-13 17:29:00 +01001669 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001670 }
1671
Philip Milnef6679c82011-09-18 11:36:57 -07001672 // External entry points
1673
1674 public int[] getLeadingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001675 if (leadingMargins == null) {
1676 leadingMargins = new int[getCount() + 1];
1677 }
1678 if (!leadingMarginsValid) {
1679 computeMargins(true);
1680 leadingMarginsValid = true;
1681 }
1682 return leadingMargins;
1683 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001684
Philip Milnef6679c82011-09-18 11:36:57 -07001685 public int[] getTrailingMargins() {
Philip Milneaa616f32011-05-27 18:38:01 -07001686 if (trailingMargins == null) {
1687 trailingMargins = new int[getCount() + 1];
1688 }
1689 if (!trailingMarginsValid) {
1690 computeMargins(false);
1691 trailingMarginsValid = true;
1692 }
1693 return trailingMargins;
1694 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001695
Philip Milne48b55242011-06-29 11:09:45 -07001696 private void computeLocations(int[] a) {
Philip Milnef6679c82011-09-18 11:36:57 -07001697 solve(getArcs(), a);
Philip Milne4a145d72011-09-29 14:14:25 -07001698 if (!orderPreserved) {
1699 // Solve returns the smallest solution to the constraint system for which all
1700 // values are positive. One value is therefore zero - though if the row/col
1701 // order is not preserved this may not be the first vertex. For consistency,
1702 // translate all the values so that they measure the distance from a[0]; the
1703 // leading edge of the parent. After this transformation some values may be
1704 // negative.
1705 int a0 = a[0];
1706 for (int i = 0, N = a.length; i < N; i++) {
1707 a[i] = a[i] - a0;
1708 }
1709 }
Philip Milneaa616f32011-05-27 18:38:01 -07001710 }
1711
Philip Milnef6679c82011-09-18 11:36:57 -07001712 public int[] getLocations() {
Philip Milneaa616f32011-05-27 18:38:01 -07001713 if (locations == null) {
1714 int N = getCount() + 1;
1715 locations = new int[N];
1716 }
Philip Milne48b55242011-06-29 11:09:45 -07001717 if (!locationsValid) {
1718 computeLocations(locations);
1719 locationsValid = true;
1720 }
Philip Milneaa616f32011-05-27 18:38:01 -07001721 return locations;
1722 }
1723
Philip Milne3f8956d2011-05-13 17:29:00 +01001724 private int size(int[] locations) {
Philip Milne4a145d72011-09-29 14:14:25 -07001725 // The parental edges are attached to vertices 0 and N - even when order is not
1726 // being preserved and other vertices fall outside this range. Measure the distance
1727 // between vertices 0 and N, assuming that locations[0] = 0.
1728 return locations[getCount()];
Philip Milne3f8956d2011-05-13 17:29:00 +01001729 }
1730
Philip Milne48b55242011-06-29 11:09:45 -07001731 private void setParentConstraints(int min, int max) {
1732 parentMin.value = min;
1733 parentMax.value = -max;
1734 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001735 }
1736
Philip Milne48b55242011-06-29 11:09:45 -07001737 private int getMeasure(int min, int max) {
1738 setParentConstraints(min, max);
1739 return size(getLocations());
1740 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001741
Philip Milnef6679c82011-09-18 11:36:57 -07001742 public int getMeasure(int measureSpec) {
Philip Milne48b55242011-06-29 11:09:45 -07001743 int mode = MeasureSpec.getMode(measureSpec);
1744 int size = MeasureSpec.getSize(measureSpec);
1745 switch (mode) {
1746 case MeasureSpec.UNSPECIFIED: {
Philip Milne93cd6a62011-07-12 14:49:45 -07001747 return getMeasure(0, MAX_SIZE);
Philip Milne48b55242011-06-29 11:09:45 -07001748 }
1749 case MeasureSpec.EXACTLY: {
1750 return getMeasure(size, size);
1751 }
1752 case MeasureSpec.AT_MOST: {
1753 return getMeasure(0, size);
1754 }
1755 default: {
1756 assert false;
1757 return 0;
1758 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001759 }
Philip Milne48b55242011-06-29 11:09:45 -07001760 }
Philip Milne3f8956d2011-05-13 17:29:00 +01001761
Philip Milnef6679c82011-09-18 11:36:57 -07001762 public void layout(int size) {
Philip Milne48b55242011-06-29 11:09:45 -07001763 setParentConstraints(size, size);
1764 getLocations();
Philip Milne3f8956d2011-05-13 17:29:00 +01001765 }
1766
Philip Milnef6679c82011-09-18 11:36:57 -07001767 public void invalidateStructure() {
Philip Milne4a145d72011-09-29 14:14:25 -07001768 maxIndex = UNDEFINED;
Philip Milneaa616f32011-05-27 18:38:01 -07001769
Philip Milne3f8956d2011-05-13 17:29:00 +01001770 groupBounds = null;
Philip Milne48b55242011-06-29 11:09:45 -07001771 forwardLinks = null;
1772 backwardLinks = null;
1773
Philip Milneaa616f32011-05-27 18:38:01 -07001774 leadingMargins = null;
1775 trailingMargins = null;
Philip Milnec9885f62011-06-15 17:07:35 -07001776 arcs = null;
Philip Milne48b55242011-06-29 11:09:45 -07001777
Philip Milneaa616f32011-05-27 18:38:01 -07001778 locations = null;
Philip Milne3f8956d2011-05-13 17:29:00 +01001779
1780 invalidateValues();
1781 }
1782
Philip Milnef6679c82011-09-18 11:36:57 -07001783 public void invalidateValues() {
Philip Milne3f8956d2011-05-13 17:29:00 +01001784 groupBoundsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001785 forwardLinksValid = false;
1786 backwardLinksValid = false;
1787
Philip Milneaa616f32011-05-27 18:38:01 -07001788 leadingMarginsValid = false;
1789 trailingMarginsValid = false;
Philip Milne48b55242011-06-29 11:09:45 -07001790 arcsValid = false;
1791
1792 locationsValid = false;
Philip Milne3f8956d2011-05-13 17:29:00 +01001793 }
1794 }
1795
1796 /**
1797 * Layout information associated with each of the children of a GridLayout.
1798 * <p>
1799 * GridLayout supports both row and column spanning and arbitrary forms of alignment within
1800 * each cell group. The fundamental parameters associated with each cell group are
1801 * gathered into their vertical and horizontal components and stored
Philip Milne93cd6a62011-07-12 14:49:45 -07001802 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
Philip Milne6216e872012-02-16 17:15:50 -08001803 * {@link GridLayout.Spec Specs} are immutable structures
Philip Milneb0ce49b2011-07-15 15:39:07 -07001804 * and may be shared between the layout parameters of different children.
Philip Milne3f8956d2011-05-13 17:29:00 +01001805 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001806 * The row and column specs contain the leading and trailing indices along each axis
Philip Milneaa616f32011-05-27 18:38:01 -07001807 * and together specify the four grid indices that delimit the cells of this cell group.
Philip Milne3f8956d2011-05-13 17:29:00 +01001808 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07001809 * The alignment properties of the row and column specs together specify
Philip Milne3f8956d2011-05-13 17:29:00 +01001810 * both aspects of alignment within the cell group. It is also possible to specify a child's
1811 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
1812 * method.
Philip Milnef6679c82011-09-18 11:36:57 -07001813 *
1814 * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
1815 *
1816 * Because the default values of the {@link #width} and {@link #height}
1817 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
1818 * declared in the layout parameters of GridLayout's children. In addition,
1819 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
1820 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
1821 * instead controlled by the principle of <em>flexibility</em>,
1822 * as discussed in {@link GridLayout}.
1823 *
1824 * <h4>Summary</h4>
1825 *
1826 * You should not need to use either of the special size values:
1827 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
1828 * a GridLayout.
Philip Milne3f8956d2011-05-13 17:29:00 +01001829 *
1830 * <h4>Default values</h4>
1831 *
1832 * <ul>
1833 * <li>{@link #width} = {@link #WRAP_CONTENT}</li>
1834 * <li>{@link #height} = {@link #WRAP_CONTENT}</li>
1835 * <li>{@link #topMargin} = 0 when
1836 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001837 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001838 * indicate that a default value should be computed on demand. </li>
1839 * <li>{@link #leftMargin} = 0 when
1840 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001841 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001842 * indicate that a default value should be computed on demand. </li>
1843 * <li>{@link #bottomMargin} = 0 when
1844 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001845 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001846 * indicate that a default value should be computed on demand. </li>
1847 * <li>{@link #rightMargin} = 0 when
1848 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
Philip Milne7fd94872011-06-07 20:14:17 -07001849 * {@code false}; otherwise {@link #UNDEFINED}, to
Philip Milne3f8956d2011-05-13 17:29:00 +01001850 * indicate that a default value should be computed on demand. </li>
Philip Milnef6679c82011-09-18 11:36:57 -07001851 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
1852 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
1853 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
1854 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
1855 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
Philip Milne6216e872012-02-16 17:15:50 -08001856 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01001857 * </ul>
1858 *
Philip Milnef6679c82011-09-18 11:36:57 -07001859 * See {@link GridLayout} for a more complete description of the conventions
1860 * used by GridLayout in the interpretation of the properties of this class.
1861 *
Philip Milne3f8956d2011-05-13 17:29:00 +01001862 * @attr ref android.R.styleable#GridLayout_Layout_layout_row
1863 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
Philip Milne3f8956d2011-05-13 17:29:00 +01001864 * @attr ref android.R.styleable#GridLayout_Layout_layout_column
1865 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
Philip Milne3f8956d2011-05-13 17:29:00 +01001866 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
1867 */
1868 public static class LayoutParams extends MarginLayoutParams {
1869
1870 // Default values
1871
1872 private static final int DEFAULT_WIDTH = WRAP_CONTENT;
1873 private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
1874 private static final int DEFAULT_MARGIN = UNDEFINED;
1875 private static final int DEFAULT_ROW = UNDEFINED;
1876 private static final int DEFAULT_COLUMN = UNDEFINED;
Philip Milnef4748702011-06-09 18:30:32 -07001877 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01001878 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
Philip Milne3f8956d2011-05-13 17:29:00 +01001879
1880 // TypedArray indices
1881
Philip Milneb0ce49b2011-07-15 15:39:07 -07001882 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin;
1883 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft;
1884 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop;
1885 private static final int RIGHT_MARGIN =
1886 R.styleable.ViewGroup_MarginLayout_layout_marginRight;
Philip Milne3f8956d2011-05-13 17:29:00 +01001887 private static final int BOTTOM_MARGIN =
Philip Milneb0ce49b2011-07-15 15:39:07 -07001888 R.styleable.ViewGroup_MarginLayout_layout_marginBottom;
Philip Milne3f8956d2011-05-13 17:29:00 +01001889
Philip Milneb0ce49b2011-07-15 15:39:07 -07001890 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
1891 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
Philip Milne5d1a9842011-07-07 11:47:08 -07001892
Philip Milneb0ce49b2011-07-15 15:39:07 -07001893 private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
1894 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
Philip Milne5d1a9842011-07-07 11:47:08 -07001895
Philip Milneb0ce49b2011-07-15 15:39:07 -07001896 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
Philip Milne3f8956d2011-05-13 17:29:00 +01001897
1898 // Instance variables
1899
1900 /**
Philip Milnef6679c82011-09-18 11:36:57 -07001901 * The spec that defines the vertical characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01001902 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08001903 * If an assignment is made to this field after a measurement or layout operation
1904 * has already taken place, a call to
1905 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
1906 * must be made to notify GridLayout of the change. GridLayout is normally able
1907 * to detect when code fails to observe this rule, issue a warning and take steps to
1908 * compensate for the omission. This facility is implemented on a best effort basis
1909 * and should not be relied upon in production code - so it is best to include the above
1910 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01001911 */
Philip Milnef6679c82011-09-18 11:36:57 -07001912 public Spec rowSpec = Spec.UNDEFINED;
1913
Philip Milne3f8956d2011-05-13 17:29:00 +01001914 /**
Philip Milnef6679c82011-09-18 11:36:57 -07001915 * The spec that defines the horizontal characteristics of the cell group
Philip Milne3f8956d2011-05-13 17:29:00 +01001916 * described by these layout parameters.
Philip Milned7dd8902012-01-26 16:55:30 -08001917 * If an assignment is made to this field after a measurement or layout operation
1918 * has already taken place, a call to
1919 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
1920 * must be made to notify GridLayout of the change. GridLayout is normally able
1921 * to detect when code fails to observe this rule, issue a warning and take steps to
1922 * compensate for the omission. This facility is implemented on a best effort basis
1923 * and should not be relied upon in production code - so it is best to include the above
1924 * calls to remove the warnings as soon as it is practical.
Philip Milne3f8956d2011-05-13 17:29:00 +01001925 */
Philip Milnef6679c82011-09-18 11:36:57 -07001926 public Spec columnSpec = Spec.UNDEFINED;
Philip Milne3f8956d2011-05-13 17:29:00 +01001927
1928 // Constructors
1929
1930 private LayoutParams(
1931 int width, int height,
1932 int left, int top, int right, int bottom,
Philip Milne93cd6a62011-07-12 14:49:45 -07001933 Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001934 super(width, height);
1935 setMargins(left, top, right, bottom);
Philip Milne93cd6a62011-07-12 14:49:45 -07001936 this.rowSpec = rowSpec;
1937 this.columnSpec = columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001938 }
1939
1940 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07001941 * Constructs a new LayoutParams instance for this <code>rowSpec</code>
1942 * and <code>columnSpec</code>. All other fields are initialized with
Philip Milne3f8956d2011-05-13 17:29:00 +01001943 * default values as defined in {@link LayoutParams}.
1944 *
Philip Milne93cd6a62011-07-12 14:49:45 -07001945 * @param rowSpec the rowSpec
1946 * @param columnSpec the columnSpec
Philip Milne3f8956d2011-05-13 17:29:00 +01001947 */
Philip Milne93cd6a62011-07-12 14:49:45 -07001948 public LayoutParams(Spec rowSpec, Spec columnSpec) {
Philip Milne3f8956d2011-05-13 17:29:00 +01001949 this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
1950 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
Philip Milne93cd6a62011-07-12 14:49:45 -07001951 rowSpec, columnSpec);
Philip Milne3f8956d2011-05-13 17:29:00 +01001952 }
1953
1954 /**
1955 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
1956 */
1957 public LayoutParams() {
Philip Milnef6679c82011-09-18 11:36:57 -07001958 this(Spec.UNDEFINED, Spec.UNDEFINED);
Philip Milne3f8956d2011-05-13 17:29:00 +01001959 }
1960
1961 // Copying constructors
1962
1963 /**
1964 * {@inheritDoc}
1965 */
1966 public LayoutParams(ViewGroup.LayoutParams params) {
1967 super(params);
1968 }
1969
1970 /**
1971 * {@inheritDoc}
1972 */
1973 public LayoutParams(MarginLayoutParams params) {
1974 super(params);
1975 }
1976
1977 /**
Alan Viverette0a0e1552013-08-07 13:24:09 -07001978 * Copy constructor. Clones the width, height, margin values, row spec,
1979 * and column spec of the source.
1980 *
1981 * @param source The layout params to copy from.
Philip Milne3f8956d2011-05-13 17:29:00 +01001982 */
Alan Viverette0a0e1552013-08-07 13:24:09 -07001983 public LayoutParams(LayoutParams source) {
1984 super(source);
1985
1986 this.rowSpec = source.rowSpec;
1987 this.columnSpec = source.columnSpec;
Philip Milne3f8956d2011-05-13 17:29:00 +01001988 }
1989
1990 // AttributeSet constructors
1991
Philip Milne3f8956d2011-05-13 17:29:00 +01001992 /**
1993 * {@inheritDoc}
1994 *
1995 * Values not defined in the attribute set take the default values
1996 * defined in {@link LayoutParams}.
1997 */
1998 public LayoutParams(Context context, AttributeSet attrs) {
Philip Milne5125e212011-07-21 11:39:37 -07001999 super(context, attrs);
2000 reInitSuper(context, attrs);
2001 init(context, attrs);
Philip Milne3f8956d2011-05-13 17:29:00 +01002002 }
2003
2004 // Implementation
2005
Philip Milne3f8956d2011-05-13 17:29:00 +01002006 // Reinitialise the margins using a different default policy than MarginLayoutParams.
2007 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
2008 // so that a layout manager default can be accessed post set up. We need this as, at the
2009 // point of installation, we do not know how many rows/cols there are and therefore
2010 // which elements are positioned next to the container's trailing edges. We need to
2011 // know this as margins around the container's boundary should have different
2012 // defaults to those between peers.
2013
2014 // This method could be parametrized and moved into MarginLayout.
2015 private void reInitSuper(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07002016 TypedArray a =
2017 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
Philip Milne3f8956d2011-05-13 17:29:00 +01002018 try {
2019 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
2020
2021 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
2022 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
2023 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
2024 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
2025 } finally {
2026 a.recycle();
2027 }
2028 }
2029
Philip Milne5125e212011-07-21 11:39:37 -07002030 private void init(Context context, AttributeSet attrs) {
Philip Milneb0ce49b2011-07-15 15:39:07 -07002031 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
Philip Milne3f8956d2011-05-13 17:29:00 +01002032 try {
Philip Milne5125e212011-07-21 11:39:37 -07002033 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
Philip Milne3f8956d2011-05-13 17:29:00 +01002034
Philip Milne1e548252011-06-16 19:02:33 -07002035 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
Philip Milne5125e212011-07-21 11:39:37 -07002036 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne899d5922011-07-21 11:39:37 -07002037 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01002038
Philip Milne1e548252011-06-16 19:02:33 -07002039 int row = a.getInt(ROW, DEFAULT_ROW);
2040 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
Philip Milne899d5922011-07-21 11:39:37 -07002041 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
Philip Milne3f8956d2011-05-13 17:29:00 +01002042 } finally {
2043 a.recycle();
2044 }
2045 }
2046
2047 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002048 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
Philip Milne6216e872012-02-16 17:15:50 -08002049 * See {@link Gravity}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002050 *
Philip Milne7fd94872011-06-07 20:14:17 -07002051 * @param gravity the new gravity value
Philip Milne3f8956d2011-05-13 17:29:00 +01002052 *
2053 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
2054 */
2055 public void setGravity(int gravity) {
Philip Milne5125e212011-07-21 11:39:37 -07002056 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
2057 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
Philip Milne3f8956d2011-05-13 17:29:00 +01002058 }
2059
2060 @Override
2061 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
2062 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
2063 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
2064 }
2065
Philip Milnef6679c82011-09-18 11:36:57 -07002066 final void setRowSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002067 rowSpec = rowSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002068 }
2069
Philip Milnef6679c82011-09-18 11:36:57 -07002070 final void setColumnSpecSpan(Interval span) {
Philip Milne93cd6a62011-07-12 14:49:45 -07002071 columnSpec = columnSpec.copyWriteSpan(span);
Philip Milne3f8956d2011-05-13 17:29:00 +01002072 }
Philip Milned7dd8902012-01-26 16:55:30 -08002073
2074 @Override
2075 public boolean equals(Object o) {
2076 if (this == o) return true;
2077 if (o == null || getClass() != o.getClass()) return false;
2078
2079 LayoutParams that = (LayoutParams) o;
2080
2081 if (!columnSpec.equals(that.columnSpec)) return false;
2082 if (!rowSpec.equals(that.rowSpec)) return false;
2083
2084 return true;
2085 }
2086
2087 @Override
2088 public int hashCode() {
2089 int result = rowSpec.hashCode();
2090 result = 31 * result + columnSpec.hashCode();
2091 return result;
2092 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002093 }
2094
Philip Milneaa616f32011-05-27 18:38:01 -07002095 /*
2096 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
2097 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
2098 */
Philip Milnef6679c82011-09-18 11:36:57 -07002099 final static class Arc {
Philip Milne3f8956d2011-05-13 17:29:00 +01002100 public final Interval span;
Philip Milneaa616f32011-05-27 18:38:01 -07002101 public final MutableInt value;
Philip Milne48b55242011-06-29 11:09:45 -07002102 public boolean valid = true;
Philip Milne3f8956d2011-05-13 17:29:00 +01002103
Philip Milneaa616f32011-05-27 18:38:01 -07002104 public Arc(Interval span, MutableInt value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002105 this.span = span;
2106 this.value = value;
2107 }
2108
2109 @Override
2110 public String toString() {
Philip Milne48b55242011-06-29 11:09:45 -07002111 return span + " " + (!valid ? "+>" : "->") + " " + value;
Philip Milne3f8956d2011-05-13 17:29:00 +01002112 }
2113 }
2114
2115 // A mutable Integer - used to avoid heap allocation during the layout operation
2116
Philip Milnef6679c82011-09-18 11:36:57 -07002117 final static class MutableInt {
Philip Milne3f8956d2011-05-13 17:29:00 +01002118 public int value;
2119
Philip Milnef6679c82011-09-18 11:36:57 -07002120 public MutableInt() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002121 reset();
2122 }
2123
Philip Milnef6679c82011-09-18 11:36:57 -07002124 public MutableInt(int value) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002125 this.value = value;
2126 }
2127
Philip Milnef6679c82011-09-18 11:36:57 -07002128 public void reset() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002129 value = Integer.MIN_VALUE;
2130 }
Philip Milne48b55242011-06-29 11:09:45 -07002131
2132 @Override
2133 public String toString() {
2134 return Integer.toString(value);
2135 }
2136 }
2137
Philip Milnef6679c82011-09-18 11:36:57 -07002138 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> {
Philip Milne48b55242011-06-29 11:09:45 -07002139 private final Class<K> keyType;
2140 private final Class<V> valueType;
2141
2142 private Assoc(Class<K> keyType, Class<V> valueType) {
2143 this.keyType = keyType;
2144 this.valueType = valueType;
2145 }
2146
Philip Milnef6679c82011-09-18 11:36:57 -07002147 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) {
Philip Milne48b55242011-06-29 11:09:45 -07002148 return new Assoc<K, V>(keyType, valueType);
2149 }
2150
2151 public void put(K key, V value) {
2152 add(Pair.create(key, value));
2153 }
2154
2155 @SuppressWarnings(value = "unchecked")
2156 public PackedMap<K, V> pack() {
2157 int N = size();
2158 K[] keys = (K[]) Array.newInstance(keyType, N);
2159 V[] values = (V[]) Array.newInstance(valueType, N);
2160 for (int i = 0; i < N; i++) {
2161 keys[i] = get(i).first;
2162 values[i] = get(i).second;
2163 }
2164 return new PackedMap<K, V>(keys, values);
2165 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002166 }
2167
Philip Milneaa616f32011-05-27 18:38:01 -07002168 /*
2169 This data structure is used in place of a Map where we have an index that refers to the order
2170 in which each key/value pairs were added to the map. In this case we store keys and values
2171 in arrays of a length that is equal to the number of unique keys. We also maintain an
2172 array of indexes from insertion order to the compacted arrays of keys and values.
2173
2174 Note that behavior differs from that of a LinkedHashMap in that repeated entries
2175 *do* get added multiples times. So the length of index is equals to the number of
2176 items added.
2177
2178 This is useful in the GridLayout class where we can rely on the order of children not
2179 changing during layout - to use integer-based lookup for our internal structures
2180 rather than using (and storing) an implementation of Map<Key, ?>.
2181 */
Philip Milne3f8956d2011-05-13 17:29:00 +01002182 @SuppressWarnings(value = "unchecked")
Philip Milnef6679c82011-09-18 11:36:57 -07002183 final static class PackedMap<K, V> {
Philip Milne3f8956d2011-05-13 17:29:00 +01002184 public final int[] index;
2185 public final K[] keys;
2186 public final V[] values;
2187
2188 private PackedMap(K[] keys, V[] values) {
2189 this.index = createIndex(keys);
2190
Philip Milneaa616f32011-05-27 18:38:01 -07002191 this.keys = compact(keys, index);
2192 this.values = compact(values, index);
Philip Milne3f8956d2011-05-13 17:29:00 +01002193 }
2194
Philip Milnef6679c82011-09-18 11:36:57 -07002195 public V getValue(int i) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002196 return values[index[i]];
2197 }
2198
2199 private static <K> int[] createIndex(K[] keys) {
2200 int size = keys.length;
2201 int[] result = new int[size];
2202
2203 Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
2204 for (int i = 0; i < size; i++) {
2205 K key = keys[i];
2206 Integer index = keyToIndex.get(key);
2207 if (index == null) {
2208 index = keyToIndex.size();
2209 keyToIndex.put(key, index);
2210 }
2211 result[i] = index;
2212 }
2213 return result;
2214 }
2215
Philip Milneaa616f32011-05-27 18:38:01 -07002216 /*
2217 Create a compact array of keys or values using the supplied index.
2218 */
2219 private static <K> K[] compact(K[] a, int[] index) {
2220 int size = a.length;
2221 Class<?> componentType = a.getClass().getComponentType();
Philip Milne51f17d52011-06-13 10:44:49 -07002222 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1);
Philip Milne3f8956d2011-05-13 17:29:00 +01002223
2224 // this overwrite duplicates, retaining the last equivalent entry
2225 for (int i = 0; i < size; i++) {
Philip Milneaa616f32011-05-27 18:38:01 -07002226 result[index[i]] = a[i];
Philip Milne3f8956d2011-05-13 17:29:00 +01002227 }
2228 return result;
2229 }
2230 }
2231
Philip Milneaa616f32011-05-27 18:38:01 -07002232 /*
Philip Milne93cd6a62011-07-12 14:49:45 -07002233 For each group (with a given alignment) we need to store the amount of space required
Philip Milne7fd94872011-06-07 20:14:17 -07002234 before the alignment point and the amount of space required after it. One side of this
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002235 calculation is always 0 for START and END alignments but we don't make use of this.
Philip Milneaa616f32011-05-27 18:38:01 -07002236 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
2237 simple optimisations are possible.
2238
2239 The general algorithm therefore is to create a Map (actually a PackedMap) from
Philip Milne93cd6a62011-07-12 14:49:45 -07002240 group to Bounds and to loop through all Views in the group taking the maximum
Philip Milneaa616f32011-05-27 18:38:01 -07002241 of the values for each View.
2242 */
Philip Milnef6679c82011-09-18 11:36:57 -07002243 static class Bounds {
Philip Milne7fd94872011-06-07 20:14:17 -07002244 public int before;
2245 public int after;
Philip Milne5125e212011-07-21 11:39:37 -07002246 public int flexibility; // we're flexible iff all included specs are flexible
Philip Milne3f8956d2011-05-13 17:29:00 +01002247
2248 private Bounds() {
2249 reset();
2250 }
2251
Philip Milnea1f7b102011-06-23 11:10:13 -07002252 protected void reset() {
Philip Milne7fd94872011-06-07 20:14:17 -07002253 before = Integer.MIN_VALUE;
2254 after = Integer.MIN_VALUE;
Philip Milne5125e212011-07-21 11:39:37 -07002255 flexibility = CAN_STRETCH; // from the above, we're flexible when empty
Philip Milne3f8956d2011-05-13 17:29:00 +01002256 }
2257
Philip Milnea1f7b102011-06-23 11:10:13 -07002258 protected void include(int before, int after) {
Philip Milne7fd94872011-06-07 20:14:17 -07002259 this.before = max(this.before, before);
2260 this.after = max(this.after, after);
Philip Milne3f8956d2011-05-13 17:29:00 +01002261 }
2262
Philip Milne48b55242011-06-29 11:09:45 -07002263 protected int size(boolean min) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002264 if (!min) {
Philip Milne5125e212011-07-21 11:39:37 -07002265 if (canStretch(flexibility)) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002266 return MAX_SIZE;
2267 }
Philip Milne48b55242011-06-29 11:09:45 -07002268 }
Philip Milne7fd94872011-06-07 20:14:17 -07002269 return before + after;
Philip Milne3f8956d2011-05-13 17:29:00 +01002270 }
2271
Philip Milne1557fd72012-04-04 23:41:34 -07002272 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
Philip Milne7a23b492012-04-24 22:12:36 -07002273 return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne1557fd72012-04-04 23:41:34 -07002274 }
2275
2276 protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
Philip Milne5125e212011-07-21 11:39:37 -07002277 this.flexibility &= spec.getFlexibility();
Philip Milne1557fd72012-04-04 23:41:34 -07002278 boolean horizontal = axis.horizontal;
2279 int size = gl.getMeasurementIncludingMargin(c, horizontal);
2280 Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002281 // todo test this works correctly when the returned value is UNDEFINED
Philip Milne7a23b492012-04-24 22:12:36 -07002282 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
Philip Milne48b55242011-06-29 11:09:45 -07002283 include(before, size - before);
Philip Milnea1f7b102011-06-23 11:10:13 -07002284 }
2285
Philip Milne3f8956d2011-05-13 17:29:00 +01002286 @Override
2287 public String toString() {
2288 return "Bounds{" +
Philip Milne7fd94872011-06-07 20:14:17 -07002289 "before=" + before +
2290 ", after=" + after +
Philip Milne3f8956d2011-05-13 17:29:00 +01002291 '}';
2292 }
2293 }
2294
2295 /**
2296 * An Interval represents a contiguous range of values that lie between
2297 * the interval's {@link #min} and {@link #max} values.
2298 * <p>
2299 * Intervals are immutable so may be passed as values and used as keys in hash tables.
2300 * It is not necessary to have multiple instances of Intervals which have the same
2301 * {@link #min} and {@link #max} values.
2302 * <p>
Philip Milne7fd94872011-06-07 20:14:17 -07002303 * Intervals are often written as {@code [min, max]} and represent the set of values
2304 * {@code x} such that {@code min <= x < max}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002305 */
Philip Milnef6679c82011-09-18 11:36:57 -07002306 final static class Interval {
Philip Milne3f8956d2011-05-13 17:29:00 +01002307 /**
2308 * The minimum value.
2309 */
2310 public final int min;
Philip Milneaa616f32011-05-27 18:38:01 -07002311
Philip Milne3f8956d2011-05-13 17:29:00 +01002312 /**
2313 * The maximum value.
2314 */
2315 public final int max;
2316
2317 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002318 * Construct a new Interval, {@code interval}, where:
Philip Milne3f8956d2011-05-13 17:29:00 +01002319 * <ul>
Philip Milne7fd94872011-06-07 20:14:17 -07002320 * <li> {@code interval.min = min} </li>
2321 * <li> {@code interval.max = max} </li>
Philip Milne3f8956d2011-05-13 17:29:00 +01002322 * </ul>
2323 *
2324 * @param min the minimum value.
2325 * @param max the maximum value.
2326 */
2327 public Interval(int min, int max) {
2328 this.min = min;
2329 this.max = max;
2330 }
2331
Philip Milnef6679c82011-09-18 11:36:57 -07002332 int size() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002333 return max - min;
2334 }
2335
Philip Milnef6679c82011-09-18 11:36:57 -07002336 Interval inverse() {
Philip Milne3f8956d2011-05-13 17:29:00 +01002337 return new Interval(max, min);
2338 }
2339
2340 /**
Philip Milne7fd94872011-06-07 20:14:17 -07002341 * Returns {@code true} if the {@link #getClass class},
2342 * {@link #min} and {@link #max} properties of this Interval and the
2343 * supplied parameter are pairwise equal; {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002344 *
Philip Milne7fd94872011-06-07 20:14:17 -07002345 * @param that the object to compare this interval with
Philip Milne3f8956d2011-05-13 17:29:00 +01002346 *
2347 * @return {@code true} if the specified object is equal to this
Philip Milne7fd94872011-06-07 20:14:17 -07002348 * {@code Interval}, {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002349 */
2350 @Override
2351 public boolean equals(Object that) {
2352 if (this == that) {
2353 return true;
2354 }
2355 if (that == null || getClass() != that.getClass()) {
2356 return false;
2357 }
2358
2359 Interval interval = (Interval) that;
2360
2361 if (max != interval.max) {
2362 return false;
2363 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002364 //noinspection RedundantIfStatement
Philip Milne3f8956d2011-05-13 17:29:00 +01002365 if (min != interval.min) {
2366 return false;
2367 }
2368
2369 return true;
2370 }
2371
2372 @Override
2373 public int hashCode() {
2374 int result = min;
2375 result = 31 * result + max;
2376 return result;
2377 }
2378
2379 @Override
2380 public String toString() {
2381 return "[" + min + ", " + max + "]";
2382 }
2383 }
2384
Philip Milne899d5922011-07-21 11:39:37 -07002385 /**
2386 * A Spec defines the horizontal or vertical characteristics of a group of
Philip Milne4a145d72011-09-29 14:14:25 -07002387 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em>
2388 * along the appropriate axis.
Philip Milne899d5922011-07-21 11:39:37 -07002389 * <p>
2390 * The <em>grid indices</em> are the leading and trailing edges of this cell group.
2391 * See {@link GridLayout} for a description of the conventions used by GridLayout
2392 * for grid indices.
2393 * <p>
2394 * The <em>alignment</em> property specifies how cells should be aligned in this group.
2395 * For row groups, this specifies the vertical alignment.
2396 * For column groups, this specifies the horizontal alignment.
Philip Milne4a145d72011-09-29 14:14:25 -07002397 * <p>
2398 * Use the following static methods to create specs:
2399 * <ul>
2400 * <li>{@link #spec(int)}</li>
2401 * <li>{@link #spec(int, int)}</li>
2402 * <li>{@link #spec(int, Alignment)}</li>
2403 * <li>{@link #spec(int, int, Alignment)}</li>
2404 * </ul>
2405 *
Philip Milne899d5922011-07-21 11:39:37 -07002406 */
Philip Milne93cd6a62011-07-12 14:49:45 -07002407 public static class Spec {
Philip Milnef6679c82011-09-18 11:36:57 -07002408 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
2409
2410 final boolean startDefined;
Philip Milne48b55242011-06-29 11:09:45 -07002411 final Interval span;
Philip Milne93cd6a62011-07-12 14:49:45 -07002412 final Alignment alignment;
Philip Milne3f8956d2011-05-13 17:29:00 +01002413
Philip Milnef6679c82011-09-18 11:36:57 -07002414 private Spec(boolean startDefined, Interval span, Alignment alignment) {
2415 this.startDefined = startDefined;
Philip Milne5d1a9842011-07-07 11:47:08 -07002416 this.span = span;
2417 this.alignment = alignment;
Philip Milne5d1a9842011-07-07 11:47:08 -07002418 }
2419
Philip Milnef6679c82011-09-18 11:36:57 -07002420 private Spec(boolean startDefined, int start, int size, Alignment alignment) {
2421 this(startDefined, new Interval(start, start + size), alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002422 }
2423
Philip Milnef6679c82011-09-18 11:36:57 -07002424 final Spec copyWriteSpan(Interval span) {
2425 return new Spec(startDefined, span, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002426 }
2427
Philip Milnef6679c82011-09-18 11:36:57 -07002428 final Spec copyWriteAlignment(Alignment alignment) {
2429 return new Spec(startDefined, span, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002430 }
2431
Philip Milnef6679c82011-09-18 11:36:57 -07002432 final int getFlexibility() {
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002433 return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
Philip Milne5d1a9842011-07-07 11:47:08 -07002434 }
2435
Philip Milne3f8956d2011-05-13 17:29:00 +01002436 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002437 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
2438 * properties of this Spec and the supplied parameter are pairwise equal,
Philip Milne7fd94872011-06-07 20:14:17 -07002439 * {@code false} otherwise.
Philip Milne3f8956d2011-05-13 17:29:00 +01002440 *
Philip Milne93cd6a62011-07-12 14:49:45 -07002441 * @param that the object to compare this spec with
Philip Milne3f8956d2011-05-13 17:29:00 +01002442 *
2443 * @return {@code true} if the specified object is equal to this
Philip Milne93cd6a62011-07-12 14:49:45 -07002444 * {@code Spec}; {@code false} otherwise
Philip Milne3f8956d2011-05-13 17:29:00 +01002445 */
2446 @Override
2447 public boolean equals(Object that) {
2448 if (this == that) {
2449 return true;
2450 }
2451 if (that == null || getClass() != that.getClass()) {
2452 return false;
2453 }
2454
Philip Milne93cd6a62011-07-12 14:49:45 -07002455 Spec spec = (Spec) that;
Philip Milne3f8956d2011-05-13 17:29:00 +01002456
Philip Milne93cd6a62011-07-12 14:49:45 -07002457 if (!alignment.equals(spec.alignment)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002458 return false;
2459 }
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002460 //noinspection RedundantIfStatement
Philip Milne93cd6a62011-07-12 14:49:45 -07002461 if (!span.equals(spec.span)) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002462 return false;
2463 }
2464
2465 return true;
2466 }
2467
2468 @Override
2469 public int hashCode() {
2470 int result = span.hashCode();
2471 result = 31 * result + alignment.hashCode();
2472 return result;
2473 }
2474 }
2475
Philip Milne3f8956d2011-05-13 17:29:00 +01002476 /**
Philip Milne93cd6a62011-07-12 14:49:45 -07002477 * Return a Spec, {@code spec}, where:
2478 * <ul>
2479 * <li> {@code spec.span = [start, start + size]} </li>
2480 * <li> {@code spec.alignment = alignment} </li>
2481 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002482 * <p>
2483 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002484 *
2485 * @param start the start
2486 * @param size the size
2487 * @param alignment the alignment
2488 */
2489 public static Spec spec(int start, int size, Alignment alignment) {
Philip Milnef6679c82011-09-18 11:36:57 -07002490 return new Spec(start != UNDEFINED, start, size, alignment);
Philip Milne93cd6a62011-07-12 14:49:45 -07002491 }
2492
2493 /**
2494 * Return a Spec, {@code spec}, where:
2495 * <ul>
2496 * <li> {@code spec.span = [start, start + 1]} </li>
2497 * <li> {@code spec.alignment = alignment} </li>
2498 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002499 * <p>
2500 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne93cd6a62011-07-12 14:49:45 -07002501 *
2502 * @param start the start index
2503 * @param alignment the alignment
Philip Milne7b757812012-09-19 18:13:44 -07002504 *
2505 * @see #spec(int, int, Alignment)
Philip Milne93cd6a62011-07-12 14:49:45 -07002506 */
2507 public static Spec spec(int start, Alignment alignment) {
2508 return spec(start, 1, alignment);
2509 }
2510
2511 /**
Philip Milne5125e212011-07-21 11:39:37 -07002512 * Return a Spec, {@code spec}, where:
2513 * <ul>
2514 * <li> {@code spec.span = [start, start + size]} </li>
2515 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002516 * <p>
2517 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002518 *
2519 * @param start the start
2520 * @param size the size
Philip Milne7b757812012-09-19 18:13:44 -07002521 *
2522 * @see #spec(int, Alignment)
Philip Milne5125e212011-07-21 11:39:37 -07002523 */
2524 public static Spec spec(int start, int size) {
2525 return spec(start, size, UNDEFINED_ALIGNMENT);
2526 }
2527
2528 /**
2529 * Return a Spec, {@code spec}, where:
2530 * <ul>
2531 * <li> {@code spec.span = [start, start + 1]} </li>
2532 * </ul>
Philip Milne7b757812012-09-19 18:13:44 -07002533 * <p>
2534 * To leave the start index undefined, use the value {@link #UNDEFINED}.
Philip Milne5125e212011-07-21 11:39:37 -07002535 *
2536 * @param start the start index
Philip Milne7b757812012-09-19 18:13:44 -07002537 *
2538 * @see #spec(int, int)
Philip Milne5125e212011-07-21 11:39:37 -07002539 */
2540 public static Spec spec(int start) {
2541 return spec(start, 1);
2542 }
2543
2544 /**
Philip Milne3f8956d2011-05-13 17:29:00 +01002545 * Alignments specify where a view should be placed within a cell group and
2546 * what size it should be.
2547 * <p>
Philip Milne93cd6a62011-07-12 14:49:45 -07002548 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
2549 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
2550 * {@code alignment}. Overall placement of the view in the cell
Philip Milne3f8956d2011-05-13 17:29:00 +01002551 * group is specified by the two alignments which act along each axis independently.
2552 * <p>
Philip Milnea1f7b102011-06-23 11:10:13 -07002553 * The GridLayout class defines the most common alignments used in general layout:
Philip Milne6216e872012-02-16 17:15:50 -08002554 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
2555 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
Philip Milnea1f7b102011-06-23 11:10:13 -07002556 */
2557 /*
Philip Milnec9885f62011-06-15 17:07:35 -07002558 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
Philip Milne3f8956d2011-05-13 17:29:00 +01002559 * to return the appropriate value for the type of alignment being defined.
2560 * The enclosing algorithms position the children
Philip Milne1e548252011-06-16 19:02:33 -07002561 * so that the locations defined by the alignment values
Philip Milne3f8956d2011-05-13 17:29:00 +01002562 * are the same for all of the views in a group.
2563 * <p>
Philip Milne3f8956d2011-05-13 17:29:00 +01002564 */
Philip Milnec9885f62011-06-15 17:07:35 -07002565 public static abstract class Alignment {
Philip Milne48b55242011-06-29 11:09:45 -07002566 Alignment() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002567 }
2568
Philip Milne6216e872012-02-16 17:15:50 -08002569 abstract int getGravityOffset(View view, int cellDelta);
2570
Philip Milne3f8956d2011-05-13 17:29:00 +01002571 /**
2572 * Returns an alignment value. In the case of vertical alignments the value
2573 * returned should indicate the distance from the top of the view to the
2574 * alignment location.
2575 * For horizontal alignments measurement is made from the left edge of the component.
2576 *
Philip Milnec9885f62011-06-15 17:07:35 -07002577 * @param view the view to which this alignment should be applied
2578 * @param viewSize the measured size of the view
Philip Milne7a23b492012-04-24 22:12:36 -07002579 * @param mode the basis of alignment: CLIP or OPTICAL
Philip Milneb3a8c542011-06-20 16:02:59 -07002580 * @return the alignment value
Philip Milne3f8956d2011-05-13 17:29:00 +01002581 */
Philip Milne7a23b492012-04-24 22:12:36 -07002582 abstract int getAlignmentValue(View view, int viewSize, int mode);
Philip Milne3f8956d2011-05-13 17:29:00 +01002583
2584 /**
2585 * Returns the size of the view specified by this alignment.
2586 * In the case of vertical alignments this method should return a height; for
2587 * horizontal alignments this method should return the width.
Philip Milnec9885f62011-06-15 17:07:35 -07002588 * <p>
2589 * The default implementation returns {@code viewSize}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002590 *
Philip Milnec9885f62011-06-15 17:07:35 -07002591 * @param view the view to which this alignment should be applied
2592 * @param viewSize the measured size of the view
2593 * @param cellSize the size of the cell into which this view will be placed
Philip Milneb3a8c542011-06-20 16:02:59 -07002594 * @return the aligned size
Philip Milne3f8956d2011-05-13 17:29:00 +01002595 */
Philip Milne6216e872012-02-16 17:15:50 -08002596 int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002597 return viewSize;
2598 }
Philip Milnea1f7b102011-06-23 11:10:13 -07002599
Philip Milne48b55242011-06-29 11:09:45 -07002600 Bounds getBounds() {
Philip Milnea1f7b102011-06-23 11:10:13 -07002601 return new Bounds();
2602 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002603 }
2604
Philip Milnef6679c82011-09-18 11:36:57 -07002605 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002606 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002607 int getGravityOffset(View view, int cellDelta) {
2608 return UNDEFINED;
2609 }
2610
2611 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002612 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne5125e212011-07-21 11:39:37 -07002613 return UNDEFINED;
2614 }
2615 };
2616
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002617 /**
2618 * Indicates that a view should be aligned with the <em>start</em>
2619 * edges of the other views in its cell group.
2620 */
Philip Milnec9885f62011-06-15 17:07:35 -07002621 private static final Alignment LEADING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002622 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002623 int getGravityOffset(View view, int cellDelta) {
2624 return 0;
2625 }
2626
2627 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002628 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002629 return 0;
2630 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002631 };
2632
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002633 /**
2634 * Indicates that a view should be aligned with the <em>end</em>
2635 * edges of the other views in its cell group.
2636 */
Philip Milnec9885f62011-06-15 17:07:35 -07002637 private static final Alignment TRAILING = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002638 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002639 int getGravityOffset(View view, int cellDelta) {
2640 return cellDelta;
2641 }
2642
2643 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002644 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002645 return viewSize;
2646 }
2647 };
2648
2649 /**
2650 * Indicates that a view should be aligned with the <em>top</em>
2651 * edges of the other views in its cell group.
2652 */
2653 public static final Alignment TOP = LEADING;
2654
2655 /**
2656 * Indicates that a view should be aligned with the <em>bottom</em>
2657 * edges of the other views in its cell group.
2658 */
2659 public static final Alignment BOTTOM = TRAILING;
2660
2661 /**
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002662 * Indicates that a view should be aligned with the <em>start</em>
Philip Milne3f8956d2011-05-13 17:29:00 +01002663 * edges of the other views in its cell group.
2664 */
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002665 public static final Alignment START = LEADING;
2666
2667 /**
2668 * Indicates that a view should be aligned with the <em>end</em>
2669 * edges of the other views in its cell group.
2670 */
2671 public static final Alignment END = TRAILING;
2672
Philip Milne6216e872012-02-16 17:15:50 -08002673 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002674 return new Alignment() {
2675 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002676 int getGravityOffset(View view, int cellDelta) {
2677 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta);
2678 }
2679
2680 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002681 public int getAlignmentValue(View view, int viewSize, int mode) {
2682 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002683 }
2684 };
2685 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002686
2687 /**
2688 * Indicates that a view should be aligned with the <em>left</em>
2689 * edges of the other views in its cell group.
2690 */
Philip Milne6216e872012-02-16 17:15:50 -08002691 public static final Alignment LEFT = createSwitchingAlignment(START, END);
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002692
2693 /**
2694 * Indicates that a view should be aligned with the <em>right</em>
2695 * edges of the other views in its cell group.
2696 */
Philip Milne6216e872012-02-16 17:15:50 -08002697 public static final Alignment RIGHT = createSwitchingAlignment(END, START);
Philip Milne3f8956d2011-05-13 17:29:00 +01002698
2699 /**
2700 * Indicates that a view should be <em>centered</em> with the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002701 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
2702 * LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002703 */
Philip Milnec9885f62011-06-15 17:07:35 -07002704 public static final Alignment CENTER = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002705 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002706 int getGravityOffset(View view, int cellDelta) {
2707 return cellDelta >> 1;
2708 }
2709
2710 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002711 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002712 return viewSize >> 1;
2713 }
2714 };
2715
2716 /**
2717 * Indicates that a view should be aligned with the <em>baselines</em>
2718 * of the other views in its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002719 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002720 *
2721 * @see View#getBaseline()
2722 */
Philip Milnec9885f62011-06-15 17:07:35 -07002723 public static final Alignment BASELINE = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002724 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002725 int getGravityOffset(View view, int cellDelta) {
2726 return 0; // baseline gravity is top
2727 }
2728
2729 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002730 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milnea8416442013-02-25 10:49:39 -08002731 if (view.getVisibility() == GONE) {
2732 return 0;
2733 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002734 int baseline = view.getBaseline();
Philip Milne7b757812012-09-19 18:13:44 -07002735 return baseline == -1 ? UNDEFINED : baseline;
Philip Milnea1f7b102011-06-23 11:10:13 -07002736 }
2737
2738 @Override
2739 public Bounds getBounds() {
2740 return new Bounds() {
2741 /*
2742 In a baseline aligned row in which some components define a baseline
2743 and some don't, we need a third variable to properly account for all
2744 the sizes. This tracks the maximum size of all the components -
2745 including those that don't define a baseline.
2746 */
2747 private int size;
2748
2749 @Override
2750 protected void reset() {
2751 super.reset();
Philip Milne48b55242011-06-29 11:09:45 -07002752 size = Integer.MIN_VALUE;
Philip Milnea1f7b102011-06-23 11:10:13 -07002753 }
2754
2755 @Override
2756 protected void include(int before, int after) {
2757 super.include(before, after);
2758 size = max(size, before + after);
2759 }
2760
2761 @Override
Philip Milne48b55242011-06-29 11:09:45 -07002762 protected int size(boolean min) {
2763 return max(super.size(min), size);
Philip Milnea1f7b102011-06-23 11:10:13 -07002764 }
2765
2766 @Override
Philip Milne1557fd72012-04-04 23:41:34 -07002767 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
2768 return max(0, super.getOffset(gl, c, a, size, hrz));
Philip Milnea1f7b102011-06-23 11:10:13 -07002769 }
2770 };
Philip Milne3f8956d2011-05-13 17:29:00 +01002771 }
Philip Milne3f8956d2011-05-13 17:29:00 +01002772 };
2773
2774 /**
2775 * Indicates that a view should expanded to fit the boundaries of its cell group.
Philip Milne93cd6a62011-07-12 14:49:45 -07002776 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
2777 * {@link LayoutParams#columnSpec columnSpecs}.
Philip Milne3f8956d2011-05-13 17:29:00 +01002778 */
2779 public static final Alignment FILL = new Alignment() {
Fabrice Di Meglio47d248e2012-02-08 17:57:48 -08002780 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002781 int getGravityOffset(View view, int cellDelta) {
2782 return 0;
2783 }
2784
2785 @Override
Philip Milne7a23b492012-04-24 22:12:36 -07002786 public int getAlignmentValue(View view, int viewSize, int mode) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002787 return UNDEFINED;
2788 }
2789
Philip Milnec9885f62011-06-15 17:07:35 -07002790 @Override
Philip Milne6216e872012-02-16 17:15:50 -08002791 public int getSizeInCell(View view, int viewSize, int cellSize) {
Philip Milne3f8956d2011-05-13 17:29:00 +01002792 return cellSize;
2793 }
2794 };
Philip Milne48b55242011-06-29 11:09:45 -07002795
Philip Milnef6679c82011-09-18 11:36:57 -07002796 static boolean canStretch(int flexibility) {
Philip Milne5d1a9842011-07-07 11:47:08 -07002797 return (flexibility & CAN_STRETCH) != 0;
2798 }
2799
Philip Milne5125e212011-07-21 11:39:37 -07002800 private static final int INFLEXIBLE = 0;
Philip Milne4c8cf4c2011-08-03 11:50:50 -07002801 private static final int CAN_STRETCH = 2;
Jim Miller452eec32011-06-16 18:32:44 -07002802}