1 /* 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 17 package android.widget; 18 19 import android.annotation.IntDef; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Insets; 25 import android.graphics.Paint; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.util.LogPrinter; 29 import android.util.Pair; 30 import android.util.Printer; 31 import android.view.Gravity; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.RemoteViews.RemoteView; 35 import com.android.internal.R; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.lang.reflect.Array; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 46 import static android.view.Gravity.*; 47 import static android.view.View.MeasureSpec.EXACTLY; 48 import static android.view.View.MeasureSpec.makeMeasureSpec; 49 import static java.lang.Math.max; 50 import static java.lang.Math.min; 51 52 /** 53 * A layout that places its children in a rectangular <em>grid</em>. 54 * <p> 55 * The grid is composed of a set of infinitely thin lines that separate the 56 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced 57 * by grid <em>indices</em>. A grid with {@code N} columns 58 * has {@code N + 1} grid indices that run from {@code 0} 59 * through {@code N} inclusive. Regardless of how GridLayout is 60 * configured, grid index {@code 0} is fixed to the leading edge of the 61 * container and grid index {@code N} is fixed to its trailing edge 62 * (after padding is taken into account). 63 * 64 * <h4>Row and Column Specs</h4> 65 * 66 * Children occupy one or more contiguous cells, as defined 67 * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and 68 * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters. 69 * Each spec defines the set of rows or columns that are to be 70 * occupied; and how children should be aligned within the resulting group of cells. 71 * Although cells do not normally overlap in a GridLayout, GridLayout does 72 * not prevent children being defined to occupy the same cell or group of cells. 73 * In this case however, there is no guarantee that children will not themselves 74 * overlap after the layout operation completes. 75 * 76 * <h4>Default Cell Assignment</h4> 77 * 78 * If a child does not specify the row and column indices of the cell it 79 * wishes to occupy, GridLayout assigns cell locations automatically using its: 80 * {@link GridLayout#setOrientation(int) orientation}, 81 * {@link GridLayout#setRowCount(int) rowCount} and 82 * {@link GridLayout#setColumnCount(int) columnCount} properties. 83 * 84 * <h4>Space</h4> 85 * 86 * Space between children may be specified either by using instances of the 87 * dedicated {@link Space} view or by setting the 88 * 89 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin}, 90 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin}, 91 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and 92 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin} 93 * 94 * layout parameters. When the 95 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} 96 * property is set, default margins around children are automatically 97 * allocated based on the prevailing UI style guide for the platform. 98 * Each of the margins so defined may be independently overridden by an assignment 99 * to the appropriate layout parameter. 100 * Default values will generally produce a reasonable spacing between components 101 * but values may change between different releases of the platform. 102 * 103 * <h4>Excess Space Distribution</h4> 104 * 105 * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight. 106 * In the event that no weights are specified, the previous conventions are respected and 107 * columns and rows are taken as flexible if their views specify some form of alignment 108 * within their groups. 109 * <p> 110 * The flexibility of a view is therefore influenced by its alignment which is, 111 * in turn, typically defined by setting the 112 * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters. 113 * If either a weight or alignment were defined along a given axis then the component 114 * is taken as <em>flexible</em> in that direction. If no weight or 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 119 * 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 * weight or a gravity. To prevent a column from stretching, ensure that one of the components 126 * in the column does not define a weight or a gravity. 127 * <p> 128 * 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. To be more precise, GridLayout treats each of its layout 131 * parameters as a constraint in the a set of variables that define the grid-lines along a 132 * given axis. During layout, GridLayout solves the constraints so as to return the unique 133 * solution to those constraints for which all variables are less-than-or-equal-to 134 * the corresponding value in any other valid solution. 135 * 136 * <h4>Interpretation of GONE</h4> 137 * 138 * For layout purposes, GridLayout treats views whose visibility status is 139 * {@link View#GONE GONE}, as having zero width and height. This is subtly different from 140 * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked 141 * view was alone in a column, that column would itself collapse to zero width if and only if 142 * no gravity was defined on the view. If gravity was defined, then the gone-marked 143 * view has no effect on the layout and the container should be laid out as if the view 144 * had never been added to it. GONE views are taken to have zero weight during excess space 145 * distribution. 146 * <p> 147 * These statements apply equally to rows as well as columns, and to groups of rows or columns. 148 * 149 * <p> 150 * See {@link GridLayout.LayoutParams} for a full description of the 151 * layout parameters used by GridLayout. 152 * 153 * @attr ref android.R.styleable#GridLayout_orientation 154 * @attr ref android.R.styleable#GridLayout_rowCount 155 * @attr ref android.R.styleable#GridLayout_columnCount 156 * @attr ref android.R.styleable#GridLayout_useDefaultMargins 157 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved 158 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved 159 */ 160 @RemoteView 161 public class GridLayout extends ViewGroup { 162 163 // Public constants 164 165 /** @hide */ 166 @IntDef({HORIZONTAL, VERTICAL}) 167 @Retention(RetentionPolicy.SOURCE) 168 public @interface Orientation {} 169 170 /** 171 * The horizontal orientation. 172 */ 173 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 174 175 /** 176 * The vertical orientation. 177 */ 178 public static final int VERTICAL = LinearLayout.VERTICAL; 179 180 /** 181 * The constant used to indicate that a value is undefined. 182 * Fields can use this value to indicate that their values 183 * have not yet been set. Similarly, methods can return this value 184 * to indicate that there is no suitable value that the implementation 185 * can return. 186 * The value used for the constant (currently {@link Integer#MIN_VALUE}) is 187 * intended to avoid confusion between valid values whose sign may not be known. 188 */ 189 public static final int UNDEFINED = Integer.MIN_VALUE; 190 191 /** @hide */ 192 @IntDef({ALIGN_BOUNDS, ALIGN_MARGINS}) 193 @Retention(RetentionPolicy.SOURCE) 194 public @interface AlignmentMode {} 195 196 /** 197 * This constant is an {@link #setAlignmentMode(int) alignmentMode}. 198 * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment 199 * is made between the edges of each component's raw 200 * view boundary: i.e. the area delimited by the component's: 201 * {@link android.view.View#getTop() top}, 202 * {@link android.view.View#getLeft() left}, 203 * {@link android.view.View#getBottom() bottom} and 204 * {@link android.view.View#getRight() right} properties. 205 * <p> 206 * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode, 207 * children that belong to a row group that uses {@link #TOP} alignment will 208 * all return the same value when their {@link android.view.View#getTop()} 209 * method is called. 210 * 211 * @see #setAlignmentMode(int) 212 */ 213 public static final int ALIGN_BOUNDS = 0; 214 215 /** 216 * This constant is an {@link #setAlignmentMode(int) alignmentMode}. 217 * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS}, 218 * the bounds of each view are extended outwards, according 219 * to their margins, before the edges of the resulting rectangle are aligned. 220 * <p> 221 * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode, 222 * the quantity {@code top - layoutParams.topMargin} is the same for all children that 223 * belong to a row group that uses {@link #TOP} alignment. 224 * 225 * @see #setAlignmentMode(int) 226 */ 227 public static final int ALIGN_MARGINS = 1; 228 229 // Misc constants 230 231 static final int MAX_SIZE = 100000; 232 static final int DEFAULT_CONTAINER_MARGIN = 0; 233 static final int UNINITIALIZED_HASH = 0; 234 static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName()); 235 static final Printer NO_PRINTER = new Printer() { 236 @Override 237 public void println(String x) { 238 } 239 }; 240 241 // Defaults 242 243 private static final int DEFAULT_ORIENTATION = HORIZONTAL; 244 private static final int DEFAULT_COUNT = UNDEFINED; 245 private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false; 246 private static final boolean DEFAULT_ORDER_PRESERVED = true; 247 private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; 248 249 // TypedArray indices 250 251 private static final int ORIENTATION = R.styleable.GridLayout_orientation; 252 private static final int ROW_COUNT = R.styleable.GridLayout_rowCount; 253 private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount; 254 private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins; 255 private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode; 256 private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved; 257 private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved; 258 259 // Instance variables 260 261 final Axis mHorizontalAxis = new Axis(true); 262 final Axis mVerticalAxis = new Axis(false); 263 int mOrientation = DEFAULT_ORIENTATION; 264 boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; 265 int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; 266 int mDefaultGap; 267 int mLastLayoutParamsHashCode = UNINITIALIZED_HASH; 268 Printer mPrinter = LOG_PRINTER; 269 270 // Constructors 271 GridLayout(Context context)272 public GridLayout(Context context) { 273 this(context, null); 274 } 275 GridLayout(Context context, AttributeSet attrs)276 public GridLayout(Context context, AttributeSet attrs) { 277 this(context, attrs, 0); 278 } 279 GridLayout(Context context, AttributeSet attrs, int defStyleAttr)280 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) { 281 this(context, attrs, defStyleAttr, 0); 282 } 283 GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)284 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 285 super(context, attrs, defStyleAttr, defStyleRes); 286 mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap); 287 final TypedArray a = context.obtainStyledAttributes( 288 attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes); 289 try { 290 setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); 291 setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT)); 292 setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION)); 293 setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS)); 294 setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE)); 295 setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); 296 setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); 297 } finally { 298 a.recycle(); 299 } 300 } 301 302 // Implementation 303 304 /** 305 * Returns the current orientation. 306 * 307 * @return either {@link #HORIZONTAL} or {@link #VERTICAL} 308 * 309 * @see #setOrientation(int) 310 * 311 * @attr ref android.R.styleable#GridLayout_orientation 312 */ 313 @Orientation getOrientation()314 public int getOrientation() { 315 return mOrientation; 316 } 317 318 /** 319 * 320 * GridLayout uses the orientation property for two purposes: 321 * <ul> 322 * <li> 323 * To control the 'direction' in which default row/column indices are generated 324 * when they are not specified in a component's layout parameters. 325 * </li> 326 * <li> 327 * To control which axis should be processed first during the layout operation: 328 * when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first. 329 * </li> 330 * </ul> 331 * 332 * The order in which axes are laid out is important if, for example, the height of 333 * one of GridLayout's children is dependent on its width - and its width is, in turn, 334 * dependent on the widths of other components. 335 * <p> 336 * If your layout contains a {@link TextView} (or derivative: 337 * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is 338 * in multi-line mode (the default) it is normally best to leave GridLayout's 339 * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of 340 * deriving its height for a given width, but not the other way around. 341 * <p> 342 * Other than the effects above, orientation does not affect the actual layout operation of 343 * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if 344 * the height of the intended layout greatly exceeds its width. 345 * <p> 346 * The default value of this property is {@link #HORIZONTAL}. 347 * 348 * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL} 349 * 350 * @see #getOrientation() 351 * 352 * @attr ref android.R.styleable#GridLayout_orientation 353 */ setOrientation(@rientation int orientation)354 public void setOrientation(@Orientation int orientation) { 355 if (this.mOrientation != orientation) { 356 this.mOrientation = orientation; 357 invalidateStructure(); 358 requestLayout(); 359 } 360 } 361 362 /** 363 * Returns the current number of rows. This is either the last value that was set 364 * with {@link #setRowCount(int)} or, if no such value was set, the maximum 365 * value of each the upper bounds defined in {@link LayoutParams#rowSpec}. 366 * 367 * @return the current number of rows 368 * 369 * @see #setRowCount(int) 370 * @see LayoutParams#rowSpec 371 * 372 * @attr ref android.R.styleable#GridLayout_rowCount 373 */ getRowCount()374 public int getRowCount() { 375 return mVerticalAxis.getCount(); 376 } 377 378 /** 379 * RowCount is used only to generate default row/column indices when 380 * they are not specified by a component's layout parameters. 381 * 382 * @param rowCount the number of rows 383 * 384 * @see #getRowCount() 385 * @see LayoutParams#rowSpec 386 * 387 * @attr ref android.R.styleable#GridLayout_rowCount 388 */ setRowCount(int rowCount)389 public void setRowCount(int rowCount) { 390 mVerticalAxis.setCount(rowCount); 391 invalidateStructure(); 392 requestLayout(); 393 } 394 395 /** 396 * Returns the current number of columns. This is either the last value that was set 397 * with {@link #setColumnCount(int)} or, if no such value was set, the maximum 398 * value of each the upper bounds defined in {@link LayoutParams#columnSpec}. 399 * 400 * @return the current number of columns 401 * 402 * @see #setColumnCount(int) 403 * @see LayoutParams#columnSpec 404 * 405 * @attr ref android.R.styleable#GridLayout_columnCount 406 */ getColumnCount()407 public int getColumnCount() { 408 return mHorizontalAxis.getCount(); 409 } 410 411 /** 412 * ColumnCount is used only to generate default column/column indices when 413 * they are not specified by a component's layout parameters. 414 * 415 * @param columnCount the number of columns. 416 * 417 * @see #getColumnCount() 418 * @see LayoutParams#columnSpec 419 * 420 * @attr ref android.R.styleable#GridLayout_columnCount 421 */ setColumnCount(int columnCount)422 public void setColumnCount(int columnCount) { 423 mHorizontalAxis.setCount(columnCount); 424 invalidateStructure(); 425 requestLayout(); 426 } 427 428 /** 429 * Returns whether or not this GridLayout will allocate default margins when no 430 * corresponding layout parameters are defined. 431 * 432 * @return {@code true} if default margins should be allocated 433 * 434 * @see #setUseDefaultMargins(boolean) 435 * 436 * @attr ref android.R.styleable#GridLayout_useDefaultMargins 437 */ getUseDefaultMargins()438 public boolean getUseDefaultMargins() { 439 return mUseDefaultMargins; 440 } 441 442 /** 443 * When {@code true}, GridLayout allocates default margins around children 444 * based on the child's visual characteristics. Each of the 445 * margins so defined may be independently overridden by an assignment 446 * to the appropriate layout parameter. 447 * <p> 448 * When {@code false}, the default value of all margins is zero. 449 * <p> 450 * When setting to {@code true}, consider setting the value of the 451 * {@link #setAlignmentMode(int) alignmentMode} 452 * property to {@link #ALIGN_BOUNDS}. 453 * <p> 454 * The default value of this property is {@code false}. 455 * 456 * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins 457 * 458 * @see #getUseDefaultMargins() 459 * @see #setAlignmentMode(int) 460 * 461 * @see MarginLayoutParams#leftMargin 462 * @see MarginLayoutParams#topMargin 463 * @see MarginLayoutParams#rightMargin 464 * @see MarginLayoutParams#bottomMargin 465 * 466 * @attr ref android.R.styleable#GridLayout_useDefaultMargins 467 */ setUseDefaultMargins(boolean useDefaultMargins)468 public void setUseDefaultMargins(boolean useDefaultMargins) { 469 this.mUseDefaultMargins = useDefaultMargins; 470 requestLayout(); 471 } 472 473 /** 474 * Returns the alignment mode. 475 * 476 * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} 477 * 478 * @see #ALIGN_BOUNDS 479 * @see #ALIGN_MARGINS 480 * 481 * @see #setAlignmentMode(int) 482 * 483 * @attr ref android.R.styleable#GridLayout_alignmentMode 484 */ 485 @AlignmentMode getAlignmentMode()486 public int getAlignmentMode() { 487 return mAlignmentMode; 488 } 489 490 /** 491 * Sets the alignment mode to be used for all of the alignments between the 492 * children of this container. 493 * <p> 494 * The default value of this property is {@link #ALIGN_MARGINS}. 495 * 496 * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} 497 * 498 * @see #ALIGN_BOUNDS 499 * @see #ALIGN_MARGINS 500 * 501 * @see #getAlignmentMode() 502 * 503 * @attr ref android.R.styleable#GridLayout_alignmentMode 504 */ setAlignmentMode(@lignmentMode int alignmentMode)505 public void setAlignmentMode(@AlignmentMode int alignmentMode) { 506 this.mAlignmentMode = alignmentMode; 507 requestLayout(); 508 } 509 510 /** 511 * Returns whether or not row boundaries are ordered by their grid indices. 512 * 513 * @return {@code true} if row boundaries must appear in the order of their indices, 514 * {@code false} otherwise 515 * 516 * @see #setRowOrderPreserved(boolean) 517 * 518 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved 519 */ isRowOrderPreserved()520 public boolean isRowOrderPreserved() { 521 return mVerticalAxis.isOrderPreserved(); 522 } 523 524 /** 525 * When this property is {@code true}, GridLayout is forced to place the row boundaries 526 * so that their associated grid indices are in ascending order in the view. 527 * <p> 528 * When this property is {@code false} GridLayout is at liberty to place the vertical row 529 * boundaries in whatever order best fits the given constraints. 530 * <p> 531 * The default value of this property is {@code true}. 532 533 * @param rowOrderPreserved {@code true} to force GridLayout to respect the order 534 * of row boundaries 535 * 536 * @see #isRowOrderPreserved() 537 * 538 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved 539 */ setRowOrderPreserved(boolean rowOrderPreserved)540 public void setRowOrderPreserved(boolean rowOrderPreserved) { 541 mVerticalAxis.setOrderPreserved(rowOrderPreserved); 542 invalidateStructure(); 543 requestLayout(); 544 } 545 546 /** 547 * Returns whether or not column boundaries are ordered by their grid indices. 548 * 549 * @return {@code true} if column boundaries must appear in the order of their indices, 550 * {@code false} otherwise 551 * 552 * @see #setColumnOrderPreserved(boolean) 553 * 554 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved 555 */ isColumnOrderPreserved()556 public boolean isColumnOrderPreserved() { 557 return mHorizontalAxis.isOrderPreserved(); 558 } 559 560 /** 561 * When this property is {@code true}, GridLayout is forced to place the column boundaries 562 * so that their associated grid indices are in ascending order in the view. 563 * <p> 564 * When this property is {@code false} GridLayout is at liberty to place the horizontal column 565 * boundaries in whatever order best fits the given constraints. 566 * <p> 567 * The default value of this property is {@code true}. 568 * 569 * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order 570 * of column boundaries. 571 * 572 * @see #isColumnOrderPreserved() 573 * 574 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved 575 */ setColumnOrderPreserved(boolean columnOrderPreserved)576 public void setColumnOrderPreserved(boolean columnOrderPreserved) { 577 mHorizontalAxis.setOrderPreserved(columnOrderPreserved); 578 invalidateStructure(); 579 requestLayout(); 580 } 581 582 /** 583 * Return the printer that will log diagnostics from this layout. 584 * 585 * @see #setPrinter(android.util.Printer) 586 * 587 * @return the printer associated with this view 588 * 589 * @hide 590 */ getPrinter()591 public Printer getPrinter() { 592 return mPrinter; 593 } 594 595 /** 596 * Set the printer that will log diagnostics from this layout. 597 * The default value is created by {@link android.util.LogPrinter}. 598 * 599 * @param printer the printer associated with this layout 600 * 601 * @see #getPrinter() 602 * 603 * @hide 604 */ setPrinter(Printer printer)605 public void setPrinter(Printer printer) { 606 this.mPrinter = (printer == null) ? NO_PRINTER : printer; 607 } 608 609 // Static utility methods 610 max2(int[] a, int valueIfEmpty)611 static int max2(int[] a, int valueIfEmpty) { 612 int result = valueIfEmpty; 613 for (int i = 0, N = a.length; i < N; i++) { 614 result = Math.max(result, a[i]); 615 } 616 return result; 617 } 618 619 @SuppressWarnings("unchecked") append(T[] a, T[] b)620 static <T> T[] append(T[] a, T[] b) { 621 T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length); 622 System.arraycopy(a, 0, result, 0, a.length); 623 System.arraycopy(b, 0, result, a.length, b.length); 624 return result; 625 } 626 getAlignment(int gravity, boolean horizontal)627 static Alignment getAlignment(int gravity, boolean horizontal) { 628 int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK; 629 int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT; 630 int flags = (gravity & mask) >> shift; 631 switch (flags) { 632 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE): 633 return horizontal ? LEFT : TOP; 634 case (AXIS_SPECIFIED | AXIS_PULL_AFTER): 635 return horizontal ? RIGHT : BOTTOM; 636 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER): 637 return FILL; 638 case AXIS_SPECIFIED: 639 return CENTER; 640 case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION): 641 return START; 642 case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION): 643 return END; 644 default: 645 return UNDEFINED_ALIGNMENT; 646 } 647 } 648 649 /** @noinspection UnusedParameters*/ getDefaultMargin(View c, boolean horizontal, boolean leading)650 private int getDefaultMargin(View c, boolean horizontal, boolean leading) { 651 if (c.getClass() == Space.class) { 652 return 0; 653 } 654 return mDefaultGap / 2; 655 } 656 getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading)657 private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) { 658 return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading); 659 } 660 getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading)661 private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) { 662 if (!mUseDefaultMargins) { 663 return 0; 664 } 665 Spec spec = horizontal ? p.columnSpec : p.rowSpec; 666 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 667 Interval span = spec.span; 668 boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading; 669 boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount()); 670 671 return getDefaultMargin(c, isAtEdge, horizontal, leading); 672 } 673 getMargin1(View view, boolean horizontal, boolean leading)674 int getMargin1(View view, boolean horizontal, boolean leading) { 675 LayoutParams lp = getLayoutParams(view); 676 int margin = horizontal ? 677 (leading ? lp.leftMargin : lp.rightMargin) : 678 (leading ? lp.topMargin : lp.bottomMargin); 679 return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin; 680 } 681 getMargin(View view, boolean horizontal, boolean leading)682 private int getMargin(View view, boolean horizontal, boolean leading) { 683 if (mAlignmentMode == ALIGN_MARGINS) { 684 return getMargin1(view, horizontal, leading); 685 } else { 686 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 687 int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins(); 688 LayoutParams lp = getLayoutParams(view); 689 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 690 int index = leading ? spec.span.min : spec.span.max; 691 return margins[index]; 692 } 693 } 694 getTotalMargin(View child, boolean horizontal)695 private int getTotalMargin(View child, boolean horizontal) { 696 return getMargin(child, horizontal, true) + getMargin(child, horizontal, false); 697 } 698 fits(int[] a, int value, int start, int end)699 private static boolean fits(int[] a, int value, int start, int end) { 700 if (end > a.length) { 701 return false; 702 } 703 for (int i = start; i < end; i++) { 704 if (a[i] > value) { 705 return false; 706 } 707 } 708 return true; 709 } 710 procrusteanFill(int[] a, int start, int end, int value)711 private static void procrusteanFill(int[] a, int start, int end, int value) { 712 int length = a.length; 713 Arrays.fill(a, Math.min(start, length), Math.min(end, length), value); 714 } 715 setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan)716 private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) { 717 lp.setRowSpecSpan(new Interval(row, row + rowSpan)); 718 lp.setColumnSpecSpan(new Interval(col, col + colSpan)); 719 } 720 721 // Logic to avert infinite loops by ensuring that the cells can be placed somewhere. clip(Interval minorRange, boolean minorWasDefined, int count)722 private static int clip(Interval minorRange, boolean minorWasDefined, int count) { 723 int size = minorRange.size(); 724 if (count == 0) { 725 return size; 726 } 727 int min = minorWasDefined ? min(minorRange.min, count) : 0; 728 return min(size, count - min); 729 } 730 731 // install default indices for cells that don't define them validateLayoutParams()732 private void validateLayoutParams() { 733 final boolean horizontal = (mOrientation == HORIZONTAL); 734 final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 735 final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0; 736 737 int major = 0; 738 int minor = 0; 739 int[] maxSizes = new int[count]; 740 741 for (int i = 0, N = getChildCount(); i < N; i++) { 742 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 743 744 final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec; 745 final Interval majorRange = majorSpec.span; 746 final boolean majorWasDefined = majorSpec.startDefined; 747 final int majorSpan = majorRange.size(); 748 if (majorWasDefined) { 749 major = majorRange.min; 750 } 751 752 final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec; 753 final Interval minorRange = minorSpec.span; 754 final boolean minorWasDefined = minorSpec.startDefined; 755 final int minorSpan = clip(minorRange, minorWasDefined, count); 756 if (minorWasDefined) { 757 minor = minorRange.min; 758 } 759 760 if (count != 0) { 761 // Find suitable row/col values when at least one is undefined. 762 if (!majorWasDefined || !minorWasDefined) { 763 while (!fits(maxSizes, major, minor, minor + minorSpan)) { 764 if (minorWasDefined) { 765 major++; 766 } else { 767 if (minor + minorSpan <= count) { 768 minor++; 769 } else { 770 minor = 0; 771 major++; 772 } 773 } 774 } 775 } 776 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan); 777 } 778 779 if (horizontal) { 780 setCellGroup(lp, major, majorSpan, minor, minorSpan); 781 } else { 782 setCellGroup(lp, minor, minorSpan, major, majorSpan); 783 } 784 785 minor = minor + minorSpan; 786 } 787 } 788 invalidateStructure()789 private void invalidateStructure() { 790 mLastLayoutParamsHashCode = UNINITIALIZED_HASH; 791 mHorizontalAxis.invalidateStructure(); 792 mVerticalAxis.invalidateStructure(); 793 // This can end up being done twice. Better twice than not at all. 794 invalidateValues(); 795 } 796 invalidateValues()797 private void invalidateValues() { 798 // Need null check because requestLayout() is called in View's initializer, 799 // before we are set up. 800 if (mHorizontalAxis != null && mVerticalAxis != null) { 801 mHorizontalAxis.invalidateValues(); 802 mVerticalAxis.invalidateValues(); 803 } 804 } 805 806 /** @hide */ 807 @Override onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams)808 protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) { 809 super.onSetLayoutParams(child, layoutParams); 810 811 if (!checkLayoutParams(layoutParams)) { 812 handleInvalidParams("supplied LayoutParams are of the wrong type"); 813 } 814 815 invalidateStructure(); 816 } 817 getLayoutParams(View c)818 final LayoutParams getLayoutParams(View c) { 819 return (LayoutParams) c.getLayoutParams(); 820 } 821 handleInvalidParams(String msg)822 private static void handleInvalidParams(String msg) { 823 throw new IllegalArgumentException(msg + ". "); 824 } 825 checkLayoutParams(LayoutParams lp, boolean horizontal)826 private void checkLayoutParams(LayoutParams lp, boolean horizontal) { 827 String groupName = horizontal ? "column" : "row"; 828 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 829 Interval span = spec.span; 830 if (span.min != UNDEFINED && span.min < 0) { 831 handleInvalidParams(groupName + " indices must be positive"); 832 } 833 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 834 int count = axis.definedCount; 835 if (count != UNDEFINED) { 836 if (span.max > count) { 837 handleInvalidParams(groupName + 838 " indices (start + span) mustn't exceed the " + groupName + " count"); 839 } 840 if (span.size() > count) { 841 handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count"); 842 } 843 } 844 } 845 846 @Override checkLayoutParams(ViewGroup.LayoutParams p)847 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 848 if (!(p instanceof LayoutParams)) { 849 return false; 850 } 851 LayoutParams lp = (LayoutParams) p; 852 853 checkLayoutParams(lp, true); 854 checkLayoutParams(lp, false); 855 856 return true; 857 } 858 859 @Override generateDefaultLayoutParams()860 protected LayoutParams generateDefaultLayoutParams() { 861 return new LayoutParams(); 862 } 863 864 @Override generateLayoutParams(AttributeSet attrs)865 public LayoutParams generateLayoutParams(AttributeSet attrs) { 866 return new LayoutParams(getContext(), attrs); 867 } 868 869 @Override generateLayoutParams(ViewGroup.LayoutParams lp)870 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 871 if (sPreserveMarginParamsInLayoutParamConversion) { 872 if (lp instanceof LayoutParams) { 873 return new LayoutParams((LayoutParams) lp); 874 } else if (lp instanceof MarginLayoutParams) { 875 return new LayoutParams((MarginLayoutParams) lp); 876 } 877 } 878 return new LayoutParams(lp); 879 } 880 881 // Draw grid 882 drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint)883 private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) { 884 if (isLayoutRtl()) { 885 int width = getWidth(); 886 graphics.drawLine(width - x1, y1, width - x2, y2, paint); 887 } else { 888 graphics.drawLine(x1, y1, x2, y2, paint); 889 } 890 } 891 892 /** 893 * @hide 894 */ 895 @Override onDebugDrawMargins(Canvas canvas, Paint paint)896 protected void onDebugDrawMargins(Canvas canvas, Paint paint) { 897 // Apply defaults, so as to remove UNDEFINED values 898 LayoutParams lp = new LayoutParams(); 899 for (int i = 0; i < getChildCount(); i++) { 900 View c = getChildAt(i); 901 lp.setMargins( 902 getMargin1(c, true, true), 903 getMargin1(c, false, true), 904 getMargin1(c, true, false), 905 getMargin1(c, false, false)); 906 lp.onDebugDraw(c, canvas, paint); 907 } 908 } 909 910 /** 911 * @hide 912 */ 913 @Override onDebugDraw(Canvas canvas)914 protected void onDebugDraw(Canvas canvas) { 915 Paint paint = new Paint(); 916 paint.setStyle(Paint.Style.STROKE); 917 paint.setColor(Color.argb(50, 255, 255, 255)); 918 919 Insets insets = getOpticalInsets(); 920 921 int top = getPaddingTop() + insets.top; 922 int left = getPaddingLeft() + insets.left; 923 int right = getWidth() - getPaddingRight() - insets.right; 924 int bottom = getHeight() - getPaddingBottom() - insets.bottom; 925 926 int[] xs = mHorizontalAxis.locations; 927 if (xs != null) { 928 for (int i = 0, length = xs.length; i < length; i++) { 929 int x = left + xs[i]; 930 drawLine(canvas, x, top, x, bottom, paint); 931 } 932 } 933 934 int[] ys = mVerticalAxis.locations; 935 if (ys != null) { 936 for (int i = 0, length = ys.length; i < length; i++) { 937 int y = top + ys[i]; 938 drawLine(canvas, left, y, right, y, paint); 939 } 940 } 941 942 super.onDebugDraw(canvas); 943 } 944 945 @Override onViewAdded(View child)946 public void onViewAdded(View child) { 947 super.onViewAdded(child); 948 invalidateStructure(); 949 } 950 951 @Override onViewRemoved(View child)952 public void onViewRemoved(View child) { 953 super.onViewRemoved(child); 954 invalidateStructure(); 955 } 956 957 /** 958 * We need to call invalidateStructure() when a child's GONE flag changes state. 959 * This implementation is a catch-all, invalidating on any change in the visibility flags. 960 * 961 * @hide 962 */ 963 @Override onChildVisibilityChanged(View child, int oldVisibility, int newVisibility)964 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) { 965 super.onChildVisibilityChanged(child, oldVisibility, newVisibility); 966 if (oldVisibility == GONE || newVisibility == GONE) { 967 invalidateStructure(); 968 } 969 } 970 computeLayoutParamsHashCode()971 private int computeLayoutParamsHashCode() { 972 int result = 1; 973 for (int i = 0, N = getChildCount(); i < N; i++) { 974 View c = getChildAt(i); 975 if (c.getVisibility() == View.GONE) continue; 976 LayoutParams lp = (LayoutParams) c.getLayoutParams(); 977 result = 31 * result + lp.hashCode(); 978 } 979 return result; 980 } 981 consistencyCheck()982 private void consistencyCheck() { 983 if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) { 984 validateLayoutParams(); 985 mLastLayoutParamsHashCode = computeLayoutParamsHashCode(); 986 } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) { 987 mPrinter.println("The fields of some layout parameters were modified in between " 988 + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec."); 989 invalidateStructure(); 990 consistencyCheck(); 991 } 992 } 993 994 // Measurement 995 996 // Note: padding has already been removed from the supplied specs measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, int childWidth, int childHeight)997 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, 998 int childWidth, int childHeight) { 999 int childWidthSpec = getChildMeasureSpec(parentWidthSpec, 1000 getTotalMargin(child, true), childWidth); 1001 int childHeightSpec = getChildMeasureSpec(parentHeightSpec, 1002 getTotalMargin(child, false), childHeight); 1003 child.measure(childWidthSpec, childHeightSpec); 1004 } 1005 1006 // Note: padding has already been removed from the supplied specs measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass)1007 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) { 1008 for (int i = 0, N = getChildCount(); i < N; i++) { 1009 View c = getChildAt(i); 1010 if (c.getVisibility() == View.GONE) continue; 1011 LayoutParams lp = getLayoutParams(c); 1012 if (firstPass) { 1013 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); 1014 } else { 1015 boolean horizontal = (mOrientation == HORIZONTAL); 1016 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1017 if (spec.getAbsoluteAlignment(horizontal) == FILL) { 1018 Interval span = spec.span; 1019 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 1020 int[] locations = axis.getLocations(); 1021 int cellSize = locations[span.max] - locations[span.min]; 1022 int viewSize = cellSize - getTotalMargin(c, horizontal); 1023 if (horizontal) { 1024 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height); 1025 } else { 1026 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize); 1027 } 1028 } 1029 } 1030 } 1031 } 1032 adjust(int measureSpec, int delta)1033 static int adjust(int measureSpec, int delta) { 1034 return makeMeasureSpec( 1035 MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec)); 1036 } 1037 1038 @Override onMeasure(int widthSpec, int heightSpec)1039 protected void onMeasure(int widthSpec, int heightSpec) { 1040 consistencyCheck(); 1041 1042 /** If we have been called by {@link View#measure(int, int)}, one of width or height 1043 * is likely to have changed. We must invalidate if so. */ 1044 invalidateValues(); 1045 1046 int hPadding = getPaddingLeft() + getPaddingRight(); 1047 int vPadding = getPaddingTop() + getPaddingBottom(); 1048 1049 int widthSpecSansPadding = adjust( widthSpec, -hPadding); 1050 int heightSpecSansPadding = adjust(heightSpec, -vPadding); 1051 1052 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true); 1053 1054 int widthSansPadding; 1055 int heightSansPadding; 1056 1057 // Use the orientation property to decide which axis should be laid out first. 1058 if (mOrientation == HORIZONTAL) { 1059 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding); 1060 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false); 1061 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding); 1062 } else { 1063 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding); 1064 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false); 1065 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding); 1066 } 1067 1068 int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth()); 1069 int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight()); 1070 1071 setMeasuredDimension( 1072 resolveSizeAndState(measuredWidth, widthSpec, 0), 1073 resolveSizeAndState(measuredHeight, heightSpec, 0)); 1074 } 1075 getMeasurement(View c, boolean horizontal)1076 private int getMeasurement(View c, boolean horizontal) { 1077 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); 1078 } 1079 getMeasurementIncludingMargin(View c, boolean horizontal)1080 final int getMeasurementIncludingMargin(View c, boolean horizontal) { 1081 if (c.getVisibility() == View.GONE) { 1082 return 0; 1083 } 1084 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal); 1085 } 1086 1087 @Override requestLayout()1088 public void requestLayout() { 1089 super.requestLayout(); 1090 invalidateValues(); 1091 } 1092 1093 // Layout container 1094 1095 /** 1096 * {@inheritDoc} 1097 */ 1098 /* 1099 The layout operation is implemented by delegating the heavy lifting to the 1100 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class. 1101 Together they compute the locations of the vertical and horizontal lines of 1102 the grid (respectively!). 1103 1104 This method is then left with the simpler task of applying margins, gravity 1105 and sizing to each child view and then placing it in its cell. 1106 */ 1107 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1108 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1109 consistencyCheck(); 1110 1111 int targetWidth = right - left; 1112 int targetHeight = bottom - top; 1113 1114 int paddingLeft = getPaddingLeft(); 1115 int paddingTop = getPaddingTop(); 1116 int paddingRight = getPaddingRight(); 1117 int paddingBottom = getPaddingBottom(); 1118 1119 mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight); 1120 mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom); 1121 1122 int[] hLocations = mHorizontalAxis.getLocations(); 1123 int[] vLocations = mVerticalAxis.getLocations(); 1124 1125 for (int i = 0, N = getChildCount(); i < N; i++) { 1126 View c = getChildAt(i); 1127 if (c.getVisibility() == View.GONE) continue; 1128 LayoutParams lp = getLayoutParams(c); 1129 Spec columnSpec = lp.columnSpec; 1130 Spec rowSpec = lp.rowSpec; 1131 1132 Interval colSpan = columnSpec.span; 1133 Interval rowSpan = rowSpec.span; 1134 1135 int x1 = hLocations[colSpan.min]; 1136 int y1 = vLocations[rowSpan.min]; 1137 1138 int x2 = hLocations[colSpan.max]; 1139 int y2 = vLocations[rowSpan.max]; 1140 1141 int cellWidth = x2 - x1; 1142 int cellHeight = y2 - y1; 1143 1144 int pWidth = getMeasurement(c, true); 1145 int pHeight = getMeasurement(c, false); 1146 1147 Alignment hAlign = columnSpec.getAbsoluteAlignment(true); 1148 Alignment vAlign = rowSpec.getAbsoluteAlignment(false); 1149 1150 Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i); 1151 Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i); 1152 1153 // Gravity offsets: the location of the alignment group relative to its cell group. 1154 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true)); 1155 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true)); 1156 1157 int leftMargin = getMargin(c, true, true); 1158 int topMargin = getMargin(c, false, true); 1159 int rightMargin = getMargin(c, true, false); 1160 int bottomMargin = getMargin(c, false, false); 1161 1162 int sumMarginsX = leftMargin + rightMargin; 1163 int sumMarginsY = topMargin + bottomMargin; 1164 1165 // Alignment offsets: the location of the view relative to its alignment group. 1166 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true); 1167 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false); 1168 1169 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX); 1170 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY); 1171 1172 int dx = x1 + gravityOffsetX + alignmentOffsetX; 1173 1174 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx : 1175 targetWidth - width - paddingRight - rightMargin - dx; 1176 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin; 1177 1178 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) { 1179 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); 1180 } 1181 c.layout(cx, cy, cx + width, cy + height); 1182 } 1183 } 1184 1185 @Override getAccessibilityClassName()1186 public CharSequence getAccessibilityClassName() { 1187 return GridLayout.class.getName(); 1188 } 1189 1190 // Inner classes 1191 1192 /* 1193 This internal class houses the algorithm for computing the locations of grid lines; 1194 along either the horizontal or vertical axis. A GridLayout uses two instances of this class - 1195 distinguished by the "horizontal" flag which is true for the horizontal axis and false 1196 for the vertical one. 1197 */ 1198 final class Axis { 1199 private static final int NEW = 0; 1200 private static final int PENDING = 1; 1201 private static final int COMPLETE = 2; 1202 1203 public final boolean horizontal; 1204 1205 public int definedCount = UNDEFINED; 1206 private int maxIndex = UNDEFINED; 1207 1208 PackedMap<Spec, Bounds> groupBounds; 1209 public boolean groupBoundsValid = false; 1210 1211 PackedMap<Interval, MutableInt> forwardLinks; 1212 public boolean forwardLinksValid = false; 1213 1214 PackedMap<Interval, MutableInt> backwardLinks; 1215 public boolean backwardLinksValid = false; 1216 1217 public int[] leadingMargins; 1218 public boolean leadingMarginsValid = false; 1219 1220 public int[] trailingMargins; 1221 public boolean trailingMarginsValid = false; 1222 1223 public Arc[] arcs; 1224 public boolean arcsValid = false; 1225 1226 public int[] locations; 1227 public boolean locationsValid = false; 1228 1229 public boolean hasWeights; 1230 public boolean hasWeightsValid = false; 1231 public int[] deltas; 1232 1233 boolean orderPreserved = DEFAULT_ORDER_PRESERVED; 1234 1235 private MutableInt parentMin = new MutableInt(0); 1236 private MutableInt parentMax = new MutableInt(-MAX_SIZE); 1237 Axis(boolean horizontal)1238 private Axis(boolean horizontal) { 1239 this.horizontal = horizontal; 1240 } 1241 calculateMaxIndex()1242 private int calculateMaxIndex() { 1243 // the number Integer.MIN_VALUE + 1 comes up in undefined cells 1244 int result = -1; 1245 for (int i = 0, N = getChildCount(); i < N; i++) { 1246 View c = getChildAt(i); 1247 LayoutParams params = getLayoutParams(c); 1248 Spec spec = horizontal ? params.columnSpec : params.rowSpec; 1249 Interval span = spec.span; 1250 result = max(result, span.min); 1251 result = max(result, span.max); 1252 result = max(result, span.size()); 1253 } 1254 return result == -1 ? UNDEFINED : result; 1255 } 1256 getMaxIndex()1257 private int getMaxIndex() { 1258 if (maxIndex == UNDEFINED) { 1259 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children 1260 } 1261 return maxIndex; 1262 } 1263 getCount()1264 public int getCount() { 1265 return max(definedCount, getMaxIndex()); 1266 } 1267 setCount(int count)1268 public void setCount(int count) { 1269 if (count != UNDEFINED && count < getMaxIndex()) { 1270 handleInvalidParams((horizontal ? "column" : "row") + 1271 "Count must be greater than or equal to the maximum of all grid indices " + 1272 "(and spans) defined in the LayoutParams of each child"); 1273 } 1274 this.definedCount = count; 1275 } 1276 isOrderPreserved()1277 public boolean isOrderPreserved() { 1278 return orderPreserved; 1279 } 1280 setOrderPreserved(boolean orderPreserved)1281 public void setOrderPreserved(boolean orderPreserved) { 1282 this.orderPreserved = orderPreserved; 1283 invalidateStructure(); 1284 } 1285 createGroupBounds()1286 private PackedMap<Spec, Bounds> createGroupBounds() { 1287 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class); 1288 for (int i = 0, N = getChildCount(); i < N; i++) { 1289 View c = getChildAt(i); 1290 // we must include views that are GONE here, see introductory javadoc 1291 LayoutParams lp = getLayoutParams(c); 1292 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1293 Bounds bounds = spec.getAbsoluteAlignment(horizontal).getBounds(); 1294 assoc.put(spec, bounds); 1295 } 1296 return assoc.pack(); 1297 } 1298 computeGroupBounds()1299 private void computeGroupBounds() { 1300 Bounds[] values = groupBounds.values; 1301 for (int i = 0; i < values.length; i++) { 1302 values[i].reset(); 1303 } 1304 for (int i = 0, N = getChildCount(); i < N; i++) { 1305 View c = getChildAt(i); 1306 // we must include views that are GONE here, see introductory javadoc 1307 LayoutParams lp = getLayoutParams(c); 1308 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1309 int size = getMeasurementIncludingMargin(c, horizontal) + 1310 ((spec.weight == 0) ? 0 : getDeltas()[i]); 1311 groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size); 1312 } 1313 } 1314 getGroupBounds()1315 public PackedMap<Spec, Bounds> getGroupBounds() { 1316 if (groupBounds == null) { 1317 groupBounds = createGroupBounds(); 1318 } 1319 if (!groupBoundsValid) { 1320 computeGroupBounds(); 1321 groupBoundsValid = true; 1322 } 1323 return groupBounds; 1324 } 1325 1326 // Add values computed by alignment - taking the max of all alignments in each span createLinks(boolean min)1327 private PackedMap<Interval, MutableInt> createLinks(boolean min) { 1328 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class); 1329 Spec[] keys = getGroupBounds().keys; 1330 for (int i = 0, N = keys.length; i < N; i++) { 1331 Interval span = min ? keys[i].span : keys[i].span.inverse(); 1332 result.put(span, new MutableInt()); 1333 } 1334 return result.pack(); 1335 } 1336 computeLinks(PackedMap<Interval, MutableInt> links, boolean min)1337 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) { 1338 MutableInt[] spans = links.values; 1339 for (int i = 0; i < spans.length; i++) { 1340 spans[i].reset(); 1341 } 1342 1343 // Use getter to trigger a re-evaluation 1344 Bounds[] bounds = getGroupBounds().values; 1345 for (int i = 0; i < bounds.length; i++) { 1346 int size = bounds[i].size(min); 1347 MutableInt valueHolder = links.getValue(i); 1348 // this effectively takes the max() of the minima and the min() of the maxima 1349 valueHolder.value = max(valueHolder.value, min ? size : -size); 1350 } 1351 } 1352 getForwardLinks()1353 private PackedMap<Interval, MutableInt> getForwardLinks() { 1354 if (forwardLinks == null) { 1355 forwardLinks = createLinks(true); 1356 } 1357 if (!forwardLinksValid) { 1358 computeLinks(forwardLinks, true); 1359 forwardLinksValid = true; 1360 } 1361 return forwardLinks; 1362 } 1363 getBackwardLinks()1364 private PackedMap<Interval, MutableInt> getBackwardLinks() { 1365 if (backwardLinks == null) { 1366 backwardLinks = createLinks(false); 1367 } 1368 if (!backwardLinksValid) { 1369 computeLinks(backwardLinks, false); 1370 backwardLinksValid = true; 1371 } 1372 return backwardLinks; 1373 } 1374 include(List<Arc> arcs, Interval key, MutableInt size, boolean ignoreIfAlreadyPresent)1375 private void include(List<Arc> arcs, Interval key, MutableInt size, 1376 boolean ignoreIfAlreadyPresent) { 1377 /* 1378 Remove self referential links. 1379 These appear: 1380 . as parental constraints when GridLayout has no children 1381 . when components have been marked as GONE 1382 */ 1383 if (key.size() == 0) { 1384 return; 1385 } 1386 // this bit below should really be computed outside here - 1387 // its just to stop default (row/col > 0) constraints obliterating valid entries 1388 if (ignoreIfAlreadyPresent) { 1389 for (Arc arc : arcs) { 1390 Interval span = arc.span; 1391 if (span.equals(key)) { 1392 return; 1393 } 1394 } 1395 } 1396 arcs.add(new Arc(key, size)); 1397 } 1398 include(List<Arc> arcs, Interval key, MutableInt size)1399 private void include(List<Arc> arcs, Interval key, MutableInt size) { 1400 include(arcs, key, size, true); 1401 } 1402 1403 // Group arcs by their first vertex, returning an array of arrays. 1404 // This is linear in the number of arcs. groupArcsByFirstVertex(Arc[] arcs)1405 Arc[][] groupArcsByFirstVertex(Arc[] arcs) { 1406 int N = getCount() + 1; // the number of vertices 1407 Arc[][] result = new Arc[N][]; 1408 int[] sizes = new int[N]; 1409 for (Arc arc : arcs) { 1410 sizes[arc.span.min]++; 1411 } 1412 for (int i = 0; i < sizes.length; i++) { 1413 result[i] = new Arc[sizes[i]]; 1414 } 1415 // reuse the sizes array to hold the current last elements as we insert each arc 1416 Arrays.fill(sizes, 0); 1417 for (Arc arc : arcs) { 1418 int i = arc.span.min; 1419 result[i][sizes[i]++] = arc; 1420 } 1421 1422 return result; 1423 } 1424 topologicalSort(final Arc[] arcs)1425 private Arc[] topologicalSort(final Arc[] arcs) { 1426 return new Object() { 1427 Arc[] result = new Arc[arcs.length]; 1428 int cursor = result.length - 1; 1429 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); 1430 int[] visited = new int[getCount() + 1]; 1431 1432 void walk(int loc) { 1433 switch (visited[loc]) { 1434 case NEW: { 1435 visited[loc] = PENDING; 1436 for (Arc arc : arcsByVertex[loc]) { 1437 walk(arc.span.max); 1438 result[cursor--] = arc; 1439 } 1440 visited[loc] = COMPLETE; 1441 break; 1442 } 1443 case PENDING: { 1444 // le singe est dans l'arbre 1445 assert false; 1446 break; 1447 } 1448 case COMPLETE: { 1449 break; 1450 } 1451 } 1452 } 1453 1454 Arc[] sort() { 1455 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { 1456 walk(loc); 1457 } 1458 assert cursor == -1; 1459 return result; 1460 } 1461 }.sort(); 1462 } 1463 topologicalSort(List<Arc> arcs)1464 private Arc[] topologicalSort(List<Arc> arcs) { 1465 return topologicalSort(arcs.toArray(new Arc[arcs.size()])); 1466 } 1467 addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links)1468 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) { 1469 for (int i = 0; i < links.keys.length; i++) { 1470 Interval key = links.keys[i]; 1471 include(result, key, links.values[i], false); 1472 } 1473 } 1474 createArcs()1475 private Arc[] createArcs() { 1476 List<Arc> mins = new ArrayList<Arc>(); 1477 List<Arc> maxs = new ArrayList<Arc>(); 1478 1479 // Add the minimum values from the components. 1480 addComponentSizes(mins, getForwardLinks()); 1481 // Add the maximum values from the components. 1482 addComponentSizes(maxs, getBackwardLinks()); 1483 1484 // Add ordering constraints to prevent row/col sizes from going negative 1485 if (orderPreserved) { 1486 // Add a constraint for every row/col 1487 for (int i = 0; i < getCount(); i++) { 1488 include(mins, new Interval(i, i + 1), new MutableInt(0)); 1489 } 1490 } 1491 1492 // Add the container constraints. Use the version of include that allows 1493 // duplicate entries in case a child spans the entire grid. 1494 int N = getCount(); 1495 include(mins, new Interval(0, N), parentMin, false); 1496 include(maxs, new Interval(N, 0), parentMax, false); 1497 1498 // Sort 1499 Arc[] sMins = topologicalSort(mins); 1500 Arc[] sMaxs = topologicalSort(maxs); 1501 1502 return append(sMins, sMaxs); 1503 } 1504 computeArcs()1505 private void computeArcs() { 1506 // getting the links validates the values that are shared by the arc list 1507 getForwardLinks(); 1508 getBackwardLinks(); 1509 } 1510 getArcs()1511 public Arc[] getArcs() { 1512 if (arcs == null) { 1513 arcs = createArcs(); 1514 } 1515 if (!arcsValid) { 1516 computeArcs(); 1517 arcsValid = true; 1518 } 1519 return arcs; 1520 } 1521 relax(int[] locations, Arc entry)1522 private boolean relax(int[] locations, Arc entry) { 1523 if (!entry.valid) { 1524 return false; 1525 } 1526 Interval span = entry.span; 1527 int u = span.min; 1528 int v = span.max; 1529 int value = entry.value.value; 1530 int candidate = locations[u] + value; 1531 if (candidate > locations[v]) { 1532 locations[v] = candidate; 1533 return true; 1534 } 1535 return false; 1536 } 1537 init(int[] locations)1538 private void init(int[] locations) { 1539 Arrays.fill(locations, 0); 1540 } 1541 arcsToString(List<Arc> arcs)1542 private String arcsToString(List<Arc> arcs) { 1543 String var = horizontal ? "x" : "y"; 1544 StringBuilder result = new StringBuilder(); 1545 boolean first = true; 1546 for (Arc arc : arcs) { 1547 if (first) { 1548 first = false; 1549 } else { 1550 result = result.append(", "); 1551 } 1552 int src = arc.span.min; 1553 int dst = arc.span.max; 1554 int value = arc.value.value; 1555 result.append((src < dst) ? 1556 var + dst + "-" + var + src + ">=" + value : 1557 var + src + "-" + var + dst + "<=" + -value); 1558 1559 } 1560 return result.toString(); 1561 } 1562 1563 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) { 1564 List<Arc> culprits = new ArrayList<Arc>(); 1565 List<Arc> removed = new ArrayList<Arc>(); 1566 for (int c = 0; c < arcs.length; c++) { 1567 Arc arc = arcs[c]; 1568 if (culprits0[c]) { 1569 culprits.add(arc); 1570 } 1571 if (!arc.valid) { 1572 removed.add(arc); 1573 } 1574 } 1575 mPrinter.println(axisName + " constraints: " + arcsToString(culprits) + 1576 " are inconsistent; permanently removing: " + arcsToString(removed) + ". "); 1577 } 1578 1579 /* 1580 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N) 1581 1582 GridLayout converts its requirements into a system of linear constraints of the 1583 form: 1584 1585 x[i] - x[j] < a[k] 1586 1587 Where the x[i] are variables and the a[k] are constants. 1588 1589 For example, if the variables were instead labeled x, y, z we might have: 1590 1591 x - y < 17 1592 y - z < 23 1593 z - x < 42 1594 1595 This is a special case of the Linear Programming problem that is, in turn, 1596 equivalent to the single-source shortest paths problem on a digraph, for 1597 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution. 1598 */ 1599 private boolean solve(Arc[] arcs, int[] locations) { 1600 return solve(arcs, locations, true); 1601 } 1602 1603 private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) { 1604 String axisName = horizontal ? "horizontal" : "vertical"; 1605 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. 1606 boolean[] originalCulprits = null; 1607 1608 for (int p = 0; p < arcs.length; p++) { 1609 init(locations); 1610 1611 // We take one extra pass over traditional Bellman-Ford (and omit their final step) 1612 for (int i = 0; i < N; i++) { 1613 boolean changed = false; 1614 for (int j = 0, length = arcs.length; j < length; j++) { 1615 changed |= relax(locations, arcs[j]); 1616 } 1617 if (!changed) { 1618 if (originalCulprits != null) { 1619 logError(axisName, arcs, originalCulprits); 1620 } 1621 return true; 1622 } 1623 } 1624 1625 if (!modifyOnError) { 1626 return false; // cannot solve with these constraints 1627 } 1628 1629 boolean[] culprits = new boolean[arcs.length]; 1630 for (int i = 0; i < N; i++) { 1631 for (int j = 0, length = arcs.length; j < length; j++) { 1632 culprits[j] |= relax(locations, arcs[j]); 1633 } 1634 } 1635 1636 if (p == 0) { 1637 originalCulprits = culprits; 1638 } 1639 1640 for (int i = 0; i < arcs.length; i++) { 1641 if (culprits[i]) { 1642 Arc arc = arcs[i]; 1643 // Only remove max values, min values alone cannot be inconsistent 1644 if (arc.span.min < arc.span.max) { 1645 continue; 1646 } 1647 arc.valid = false; 1648 break; 1649 } 1650 } 1651 } 1652 return true; 1653 } 1654 1655 private void computeMargins(boolean leading) { 1656 int[] margins = leading ? leadingMargins : trailingMargins; 1657 for (int i = 0, N = getChildCount(); i < N; i++) { 1658 View c = getChildAt(i); 1659 if (c.getVisibility() == View.GONE) continue; 1660 LayoutParams lp = getLayoutParams(c); 1661 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1662 Interval span = spec.span; 1663 int index = leading ? span.min : span.max; 1664 margins[index] = max(margins[index], getMargin1(c, horizontal, leading)); 1665 } 1666 } 1667 1668 // External entry points 1669 1670 public int[] getLeadingMargins() { 1671 if (leadingMargins == null) { 1672 leadingMargins = new int[getCount() + 1]; 1673 } 1674 if (!leadingMarginsValid) { 1675 computeMargins(true); 1676 leadingMarginsValid = true; 1677 } 1678 return leadingMargins; 1679 } 1680 1681 public int[] getTrailingMargins() { 1682 if (trailingMargins == null) { 1683 trailingMargins = new int[getCount() + 1]; 1684 } 1685 if (!trailingMarginsValid) { 1686 computeMargins(false); 1687 trailingMarginsValid = true; 1688 } 1689 return trailingMargins; 1690 } 1691 1692 private boolean solve(int[] a) { 1693 return solve(getArcs(), a); 1694 } 1695 1696 private boolean computeHasWeights() { 1697 for (int i = 0, N = getChildCount(); i < N; i++) { 1698 final View child = getChildAt(i); 1699 if (child.getVisibility() == View.GONE) { 1700 continue; 1701 } 1702 LayoutParams lp = getLayoutParams(child); 1703 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1704 if (spec.weight != 0) { 1705 return true; 1706 } 1707 } 1708 return false; 1709 } 1710 1711 private boolean hasWeights() { 1712 if (!hasWeightsValid) { 1713 hasWeights = computeHasWeights(); 1714 hasWeightsValid = true; 1715 } 1716 return hasWeights; 1717 } 1718 1719 public int[] getDeltas() { 1720 if (deltas == null) { 1721 deltas = new int[getChildCount()]; 1722 } 1723 return deltas; 1724 } 1725 1726 private void shareOutDelta(int totalDelta, float totalWeight) { 1727 Arrays.fill(deltas, 0); 1728 for (int i = 0, N = getChildCount(); i < N; i++) { 1729 final View c = getChildAt(i); 1730 if (c.getVisibility() == View.GONE) { 1731 continue; 1732 } 1733 LayoutParams lp = getLayoutParams(c); 1734 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1735 float weight = spec.weight; 1736 if (weight != 0) { 1737 int delta = Math.round((weight * totalDelta / totalWeight)); 1738 deltas[i] = delta; 1739 // the two adjustments below are to counter the above rounding and avoid 1740 // off-by-ones at the end 1741 totalDelta -= delta; 1742 totalWeight -= weight; 1743 } 1744 } 1745 } 1746 1747 private void solveAndDistributeSpace(int[] a) { 1748 Arrays.fill(getDeltas(), 0); 1749 solve(a); 1750 int deltaMax = parentMin.value * getChildCount() + 1; //exclusive 1751 if (deltaMax < 2) { 1752 return; //don't have any delta to distribute 1753 } 1754 int deltaMin = 0; //inclusive 1755 1756 float totalWeight = calculateTotalWeight(); 1757 1758 int validDelta = -1; //delta for which a solution exists 1759 boolean validSolution = true; 1760 // do a binary search to find the max delta that won't conflict with constraints 1761 while(deltaMin < deltaMax) { 1762 // cast to long to prevent overflow. 1763 final int delta = (int) (((long) deltaMin + deltaMax) / 2); 1764 invalidateValues(); 1765 shareOutDelta(delta, totalWeight); 1766 validSolution = solve(getArcs(), a, false); 1767 if (validSolution) { 1768 validDelta = delta; 1769 deltaMin = delta + 1; 1770 } else { 1771 deltaMax = delta; 1772 } 1773 } 1774 if (validDelta > 0 && !validSolution) { 1775 // last solution was not successful but we have a successful one. Use it. 1776 invalidateValues(); 1777 shareOutDelta(validDelta, totalWeight); 1778 solve(a); 1779 } 1780 } 1781 1782 private float calculateTotalWeight() { 1783 float totalWeight = 0f; 1784 for (int i = 0, N = getChildCount(); i < N; i++) { 1785 View c = getChildAt(i); 1786 if (c.getVisibility() == View.GONE) { 1787 continue; 1788 } 1789 LayoutParams lp = getLayoutParams(c); 1790 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1791 totalWeight += spec.weight; 1792 } 1793 return totalWeight; 1794 } 1795 1796 private void computeLocations(int[] a) { 1797 if (!hasWeights()) { 1798 solve(a); 1799 } else { 1800 solveAndDistributeSpace(a); 1801 } 1802 if (!orderPreserved) { 1803 // Solve returns the smallest solution to the constraint system for which all 1804 // values are positive. One value is therefore zero - though if the row/col 1805 // order is not preserved this may not be the first vertex. For consistency, 1806 // translate all the values so that they measure the distance from a[0]; the 1807 // leading edge of the parent. After this transformation some values may be 1808 // negative. 1809 int a0 = a[0]; 1810 for (int i = 0, N = a.length; i < N; i++) { 1811 a[i] = a[i] - a0; 1812 } 1813 } 1814 } 1815 1816 public int[] getLocations() { 1817 if (locations == null) { 1818 int N = getCount() + 1; 1819 locations = new int[N]; 1820 } 1821 if (!locationsValid) { 1822 computeLocations(locations); 1823 locationsValid = true; 1824 } 1825 return locations; 1826 } 1827 1828 private int size(int[] locations) { 1829 // The parental edges are attached to vertices 0 and N - even when order is not 1830 // being preserved and other vertices fall outside this range. Measure the distance 1831 // between vertices 0 and N, assuming that locations[0] = 0. 1832 return locations[getCount()]; 1833 } 1834 1835 private void setParentConstraints(int min, int max) { 1836 parentMin.value = min; 1837 parentMax.value = -max; 1838 locationsValid = false; 1839 } 1840 1841 private int getMeasure(int min, int max) { 1842 setParentConstraints(min, max); 1843 return size(getLocations()); 1844 } 1845 1846 public int getMeasure(int measureSpec) { 1847 int mode = MeasureSpec.getMode(measureSpec); 1848 int size = MeasureSpec.getSize(measureSpec); 1849 switch (mode) { 1850 case MeasureSpec.UNSPECIFIED: { 1851 return getMeasure(0, MAX_SIZE); 1852 } 1853 case MeasureSpec.EXACTLY: { 1854 return getMeasure(size, size); 1855 } 1856 case MeasureSpec.AT_MOST: { 1857 return getMeasure(0, size); 1858 } 1859 default: { 1860 assert false; 1861 return 0; 1862 } 1863 } 1864 } 1865 1866 public void layout(int size) { 1867 setParentConstraints(size, size); 1868 getLocations(); 1869 } 1870 1871 public void invalidateStructure() { 1872 maxIndex = UNDEFINED; 1873 1874 groupBounds = null; 1875 forwardLinks = null; 1876 backwardLinks = null; 1877 1878 leadingMargins = null; 1879 trailingMargins = null; 1880 arcs = null; 1881 1882 locations = null; 1883 1884 deltas = null; 1885 hasWeightsValid = false; 1886 1887 invalidateValues(); 1888 } 1889 1890 public void invalidateValues() { 1891 groupBoundsValid = false; 1892 forwardLinksValid = false; 1893 backwardLinksValid = false; 1894 1895 leadingMarginsValid = false; 1896 trailingMarginsValid = false; 1897 arcsValid = false; 1898 1899 locationsValid = false; 1900 } 1901 } 1902 1903 /** 1904 * Layout information associated with each of the children of a GridLayout. 1905 * <p> 1906 * GridLayout supports both row and column spanning and arbitrary forms of alignment within 1907 * each cell group. The fundamental parameters associated with each cell group are 1908 * gathered into their vertical and horizontal components and stored 1909 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters. 1910 * {@link GridLayout.Spec Specs} are immutable structures 1911 * and may be shared between the layout parameters of different children. 1912 * <p> 1913 * The row and column specs contain the leading and trailing indices along each axis 1914 * and together specify the four grid indices that delimit the cells of this cell group. 1915 * <p> 1916 * The alignment properties of the row and column specs together specify 1917 * both aspects of alignment within the cell group. It is also possible to specify a child's 1918 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} 1919 * method. 1920 * <p> 1921 * The weight property is also included in Spec and specifies the proportion of any 1922 * excess space that is due to the associated view. 1923 * 1924 * <h4>WRAP_CONTENT and MATCH_PARENT</h4> 1925 * 1926 * Because the default values of the {@link #width} and {@link #height} 1927 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly 1928 * declared in the layout parameters of GridLayout's children. In addition, 1929 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from 1930 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is 1931 * instead controlled by the principle of <em>flexibility</em>, 1932 * as discussed in {@link GridLayout}. 1933 * 1934 * <h4>Summary</h4> 1935 * 1936 * You should not need to use either of the special size values: 1937 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of 1938 * a GridLayout. 1939 * 1940 * <h4>Default values</h4> 1941 * 1942 * <ul> 1943 * <li>{@link #width} = {@link #WRAP_CONTENT}</li> 1944 * <li>{@link #height} = {@link #WRAP_CONTENT}</li> 1945 * <li>{@link #topMargin} = 0 when 1946 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1947 * {@code false}; otherwise {@link #UNDEFINED}, to 1948 * indicate that a default value should be computed on demand. </li> 1949 * <li>{@link #leftMargin} = 0 when 1950 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1951 * {@code false}; otherwise {@link #UNDEFINED}, to 1952 * indicate that a default value should be computed on demand. </li> 1953 * <li>{@link #bottomMargin} = 0 when 1954 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1955 * {@code false}; otherwise {@link #UNDEFINED}, to 1956 * indicate that a default value should be computed on demand. </li> 1957 * <li>{@link #rightMargin} = 0 when 1958 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1959 * {@code false}; otherwise {@link #UNDEFINED}, to 1960 * indicate that a default value should be computed on demand. </li> 1961 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> 1962 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> 1963 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> 1964 * <li>{@link #rowSpec}<code>.weight</code> = 0 </li> 1965 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> 1966 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> 1967 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> 1968 * <li>{@link #columnSpec}<code>.weight</code> = 0 </li> 1969 * </ul> 1970 * 1971 * See {@link GridLayout} for a more complete description of the conventions 1972 * used by GridLayout in the interpretation of the properties of this class. 1973 * 1974 * @attr ref android.R.styleable#GridLayout_Layout_layout_row 1975 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan 1976 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight 1977 * @attr ref android.R.styleable#GridLayout_Layout_layout_column 1978 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan 1979 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight 1980 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1981 */ 1982 public static class LayoutParams extends MarginLayoutParams { 1983 1984 // Default values 1985 1986 private static final int DEFAULT_WIDTH = WRAP_CONTENT; 1987 private static final int DEFAULT_HEIGHT = WRAP_CONTENT; 1988 private static final int DEFAULT_MARGIN = UNDEFINED; 1989 private static final int DEFAULT_ROW = UNDEFINED; 1990 private static final int DEFAULT_COLUMN = UNDEFINED; 1991 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); 1992 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); 1993 1994 // TypedArray indices 1995 1996 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin; 1997 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft; 1998 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop; 1999 private static final int RIGHT_MARGIN = 2000 R.styleable.ViewGroup_MarginLayout_layout_marginRight; 2001 private static final int BOTTOM_MARGIN = 2002 R.styleable.ViewGroup_MarginLayout_layout_marginBottom; 2003 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; 2004 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; 2005 private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight; 2006 2007 private static final int ROW = R.styleable.GridLayout_Layout_layout_row; 2008 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; 2009 private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight; 2010 2011 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; 2012 2013 // Instance variables 2014 2015 /** 2016 * The spec that defines the vertical characteristics of the cell group 2017 * described by these layout parameters. 2018 * If an assignment is made to this field after a measurement or layout operation 2019 * has already taken place, a call to 2020 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 2021 * must be made to notify GridLayout of the change. GridLayout is normally able 2022 * to detect when code fails to observe this rule, issue a warning and take steps to 2023 * compensate for the omission. This facility is implemented on a best effort basis 2024 * and should not be relied upon in production code - so it is best to include the above 2025 * calls to remove the warnings as soon as it is practical. 2026 */ 2027 public Spec rowSpec = Spec.UNDEFINED; 2028 2029 /** 2030 * The spec that defines the horizontal characteristics of the cell group 2031 * described by these layout parameters. 2032 * If an assignment is made to this field after a measurement or layout operation 2033 * has already taken place, a call to 2034 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 2035 * must be made to notify GridLayout of the change. GridLayout is normally able 2036 * to detect when code fails to observe this rule, issue a warning and take steps to 2037 * compensate for the omission. This facility is implemented on a best effort basis 2038 * and should not be relied upon in production code - so it is best to include the above 2039 * calls to remove the warnings as soon as it is practical. 2040 */ 2041 public Spec columnSpec = Spec.UNDEFINED; 2042 2043 // Constructors 2044 2045 private LayoutParams( 2046 int width, int height, 2047 int left, int top, int right, int bottom, 2048 Spec rowSpec, Spec columnSpec) { 2049 super(width, height); 2050 setMargins(left, top, right, bottom); 2051 this.rowSpec = rowSpec; 2052 this.columnSpec = columnSpec; 2053 } 2054 2055 /** 2056 * Constructs a new LayoutParams instance for this <code>rowSpec</code> 2057 * and <code>columnSpec</code>. All other fields are initialized with 2058 * default values as defined in {@link LayoutParams}. 2059 * 2060 * @param rowSpec the rowSpec 2061 * @param columnSpec the columnSpec 2062 */ 2063 public LayoutParams(Spec rowSpec, Spec columnSpec) { 2064 this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 2065 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 2066 rowSpec, columnSpec); 2067 } 2068 2069 /** 2070 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 2071 */ 2072 public LayoutParams() { 2073 this(Spec.UNDEFINED, Spec.UNDEFINED); 2074 } 2075 2076 // Copying constructors 2077 2078 /** 2079 * {@inheritDoc} 2080 */ 2081 public LayoutParams(ViewGroup.LayoutParams params) { 2082 super(params); 2083 } 2084 2085 /** 2086 * {@inheritDoc} 2087 */ 2088 public LayoutParams(MarginLayoutParams params) { 2089 super(params); 2090 } 2091 2092 /** 2093 * Copy constructor. Clones the width, height, margin values, row spec, 2094 * and column spec of the source. 2095 * 2096 * @param source The layout params to copy from. 2097 */ 2098 public LayoutParams(LayoutParams source) { 2099 super(source); 2100 2101 this.rowSpec = source.rowSpec; 2102 this.columnSpec = source.columnSpec; 2103 } 2104 2105 // AttributeSet constructors 2106 2107 /** 2108 * {@inheritDoc} 2109 * 2110 * Values not defined in the attribute set take the default values 2111 * defined in {@link LayoutParams}. 2112 */ 2113 public LayoutParams(Context context, AttributeSet attrs) { 2114 super(context, attrs); 2115 reInitSuper(context, attrs); 2116 init(context, attrs); 2117 } 2118 2119 // Implementation 2120 2121 // Reinitialise the margins using a different default policy than MarginLayoutParams. 2122 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 2123 // so that a layout manager default can be accessed post set up. We need this as, at the 2124 // point of installation, we do not know how many rows/cols there are and therefore 2125 // which elements are positioned next to the container's trailing edges. We need to 2126 // know this as margins around the container's boundary should have different 2127 // defaults to those between peers. 2128 2129 // This method could be parametrized and moved into MarginLayout. 2130 private void reInitSuper(Context context, AttributeSet attrs) { 2131 TypedArray a = 2132 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 2133 try { 2134 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 2135 2136 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 2137 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 2138 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 2139 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 2140 } finally { 2141 a.recycle(); 2142 } 2143 } 2144 2145 private void init(Context context, AttributeSet attrs) { 2146 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); 2147 try { 2148 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); 2149 2150 int column = a.getInt(COLUMN, DEFAULT_COLUMN); 2151 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 2152 float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT); 2153 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight); 2154 2155 int row = a.getInt(ROW, DEFAULT_ROW); 2156 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); 2157 float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT); 2158 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight); 2159 } finally { 2160 a.recycle(); 2161 } 2162 } 2163 2164 /** 2165 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 2166 * See {@link Gravity}. 2167 * 2168 * @param gravity the new gravity value 2169 * 2170 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 2171 */ 2172 public void setGravity(int gravity) { 2173 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); 2174 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); 2175 } 2176 2177 @Override 2178 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 2179 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 2180 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 2181 } 2182 2183 final void setRowSpecSpan(Interval span) { 2184 rowSpec = rowSpec.copyWriteSpan(span); 2185 } 2186 2187 final void setColumnSpecSpan(Interval span) { 2188 columnSpec = columnSpec.copyWriteSpan(span); 2189 } 2190 2191 @Override 2192 public boolean equals(Object o) { 2193 if (this == o) return true; 2194 if (o == null || getClass() != o.getClass()) return false; 2195 2196 LayoutParams that = (LayoutParams) o; 2197 2198 if (!columnSpec.equals(that.columnSpec)) return false; 2199 if (!rowSpec.equals(that.rowSpec)) return false; 2200 2201 return true; 2202 } 2203 2204 @Override 2205 public int hashCode() { 2206 int result = rowSpec.hashCode(); 2207 result = 31 * result + columnSpec.hashCode(); 2208 return result; 2209 } 2210 } 2211 2212 /* 2213 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 2214 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 2215 */ 2216 final static class Arc { 2217 public final Interval span; 2218 public final MutableInt value; 2219 public boolean valid = true; 2220 2221 public Arc(Interval span, MutableInt value) { 2222 this.span = span; 2223 this.value = value; 2224 } 2225 2226 @Override 2227 public String toString() { 2228 return span + " " + (!valid ? "+>" : "->") + " " + value; 2229 } 2230 } 2231 2232 // A mutable Integer - used to avoid heap allocation during the layout operation 2233 2234 final static class MutableInt { 2235 public int value; 2236 2237 public MutableInt() { 2238 reset(); 2239 } 2240 2241 public MutableInt(int value) { 2242 this.value = value; 2243 } 2244 2245 public void reset() { 2246 value = Integer.MIN_VALUE; 2247 } 2248 2249 @Override 2250 public String toString() { 2251 return Integer.toString(value); 2252 } 2253 } 2254 2255 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> { 2256 private final Class<K> keyType; 2257 private final Class<V> valueType; 2258 2259 private Assoc(Class<K> keyType, Class<V> valueType) { 2260 this.keyType = keyType; 2261 this.valueType = valueType; 2262 } 2263 2264 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { 2265 return new Assoc<K, V>(keyType, valueType); 2266 } 2267 2268 public void put(K key, V value) { 2269 add(Pair.create(key, value)); 2270 } 2271 2272 @SuppressWarnings(value = "unchecked") 2273 public PackedMap<K, V> pack() { 2274 int N = size(); 2275 K[] keys = (K[]) Array.newInstance(keyType, N); 2276 V[] values = (V[]) Array.newInstance(valueType, N); 2277 for (int i = 0; i < N; i++) { 2278 keys[i] = get(i).first; 2279 values[i] = get(i).second; 2280 } 2281 return new PackedMap<K, V>(keys, values); 2282 } 2283 } 2284 2285 /* 2286 This data structure is used in place of a Map where we have an index that refers to the order 2287 in which each key/value pairs were added to the map. In this case we store keys and values 2288 in arrays of a length that is equal to the number of unique keys. We also maintain an 2289 array of indexes from insertion order to the compacted arrays of keys and values. 2290 2291 Note that behavior differs from that of a LinkedHashMap in that repeated entries 2292 *do* get added multiples times. So the length of index is equals to the number of 2293 items added. 2294 2295 This is useful in the GridLayout class where we can rely on the order of children not 2296 changing during layout - to use integer-based lookup for our internal structures 2297 rather than using (and storing) an implementation of Map<Key, ?>. 2298 */ 2299 @SuppressWarnings(value = "unchecked") 2300 final static class PackedMap<K, V> { 2301 public final int[] index; 2302 public final K[] keys; 2303 public final V[] values; 2304 2305 private PackedMap(K[] keys, V[] values) { 2306 this.index = createIndex(keys); 2307 2308 this.keys = compact(keys, index); 2309 this.values = compact(values, index); 2310 } 2311 2312 public V getValue(int i) { 2313 return values[index[i]]; 2314 } 2315 2316 private static <K> int[] createIndex(K[] keys) { 2317 int size = keys.length; 2318 int[] result = new int[size]; 2319 2320 Map<K, Integer> keyToIndex = new HashMap<K, Integer>(); 2321 for (int i = 0; i < size; i++) { 2322 K key = keys[i]; 2323 Integer index = keyToIndex.get(key); 2324 if (index == null) { 2325 index = keyToIndex.size(); 2326 keyToIndex.put(key, index); 2327 } 2328 result[i] = index; 2329 } 2330 return result; 2331 } 2332 2333 /* 2334 Create a compact array of keys or values using the supplied index. 2335 */ 2336 private static <K> K[] compact(K[] a, int[] index) { 2337 int size = a.length; 2338 Class<?> componentType = a.getClass().getComponentType(); 2339 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 2340 2341 // this overwrite duplicates, retaining the last equivalent entry 2342 for (int i = 0; i < size; i++) { 2343 result[index[i]] = a[i]; 2344 } 2345 return result; 2346 } 2347 } 2348 2349 /* 2350 For each group (with a given alignment) we need to store the amount of space required 2351 before the alignment point and the amount of space required after it. One side of this 2352 calculation is always 0 for START and END alignments but we don't make use of this. 2353 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 2354 simple optimisations are possible. 2355 2356 The general algorithm therefore is to create a Map (actually a PackedMap) from 2357 group to Bounds and to loop through all Views in the group taking the maximum 2358 of the values for each View. 2359 */ 2360 static class Bounds { 2361 public int before; 2362 public int after; 2363 public int flexibility; // we're flexible iff all included specs are flexible 2364 2365 private Bounds() { 2366 reset(); 2367 } 2368 2369 protected void reset() { 2370 before = Integer.MIN_VALUE; 2371 after = Integer.MIN_VALUE; 2372 flexibility = CAN_STRETCH; // from the above, we're flexible when empty 2373 } 2374 2375 protected void include(int before, int after) { 2376 this.before = max(this.before, before); 2377 this.after = max(this.after, after); 2378 } 2379 2380 protected int size(boolean min) { 2381 if (!min) { 2382 if (canStretch(flexibility)) { 2383 return MAX_SIZE; 2384 } 2385 } 2386 return before + after; 2387 } 2388 2389 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { 2390 return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); 2391 } 2392 2393 protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) { 2394 this.flexibility &= spec.getFlexibility(); 2395 boolean horizontal = axis.horizontal; 2396 Alignment alignment = spec.getAbsoluteAlignment(axis.horizontal); 2397 // todo test this works correctly when the returned value is UNDEFINED 2398 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); 2399 include(before, size - before); 2400 } 2401 2402 @Override 2403 public String toString() { 2404 return "Bounds{" + 2405 "before=" + before + 2406 ", after=" + after + 2407 '}'; 2408 } 2409 } 2410 2411 /** 2412 * An Interval represents a contiguous range of values that lie between 2413 * the interval's {@link #min} and {@link #max} values. 2414 * <p> 2415 * Intervals are immutable so may be passed as values and used as keys in hash tables. 2416 * It is not necessary to have multiple instances of Intervals which have the same 2417 * {@link #min} and {@link #max} values. 2418 * <p> 2419 * Intervals are often written as {@code [min, max]} and represent the set of values 2420 * {@code x} such that {@code min <= x < max}. 2421 */ 2422 final static class Interval { 2423 /** 2424 * The minimum value. 2425 */ 2426 public final int min; 2427 2428 /** 2429 * The maximum value. 2430 */ 2431 public final int max; 2432 2433 /** 2434 * Construct a new Interval, {@code interval}, where: 2435 * <ul> 2436 * <li> {@code interval.min = min} </li> 2437 * <li> {@code interval.max = max} </li> 2438 * </ul> 2439 * 2440 * @param min the minimum value. 2441 * @param max the maximum value. 2442 */ 2443 public Interval(int min, int max) { 2444 this.min = min; 2445 this.max = max; 2446 } 2447 2448 int size() { 2449 return max - min; 2450 } 2451 2452 Interval inverse() { 2453 return new Interval(max, min); 2454 } 2455 2456 /** 2457 * Returns {@code true} if the {@link #getClass class}, 2458 * {@link #min} and {@link #max} properties of this Interval and the 2459 * supplied parameter are pairwise equal; {@code false} otherwise. 2460 * 2461 * @param that the object to compare this interval with 2462 * 2463 * @return {@code true} if the specified object is equal to this 2464 * {@code Interval}, {@code false} otherwise. 2465 */ 2466 @Override 2467 public boolean equals(Object that) { 2468 if (this == that) { 2469 return true; 2470 } 2471 if (that == null || getClass() != that.getClass()) { 2472 return false; 2473 } 2474 2475 Interval interval = (Interval) that; 2476 2477 if (max != interval.max) { 2478 return false; 2479 } 2480 //noinspection RedundantIfStatement 2481 if (min != interval.min) { 2482 return false; 2483 } 2484 2485 return true; 2486 } 2487 2488 @Override 2489 public int hashCode() { 2490 int result = min; 2491 result = 31 * result + max; 2492 return result; 2493 } 2494 2495 @Override 2496 public String toString() { 2497 return "[" + min + ", " + max + "]"; 2498 } 2499 } 2500 2501 /** 2502 * A Spec defines the horizontal or vertical characteristics of a group of 2503 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em> 2504 * along the appropriate axis. 2505 * <p> 2506 * The <em>grid indices</em> are the leading and trailing edges of this cell group. 2507 * See {@link GridLayout} for a description of the conventions used by GridLayout 2508 * for grid indices. 2509 * <p> 2510 * The <em>alignment</em> property specifies how cells should be aligned in this group. 2511 * For row groups, this specifies the vertical alignment. 2512 * For column groups, this specifies the horizontal alignment. 2513 * <p> 2514 * Use the following static methods to create specs: 2515 * <ul> 2516 * <li>{@link #spec(int)}</li> 2517 * <li>{@link #spec(int, int)}</li> 2518 * <li>{@link #spec(int, Alignment)}</li> 2519 * <li>{@link #spec(int, int, Alignment)}</li> 2520 * <li>{@link #spec(int, float)}</li> 2521 * <li>{@link #spec(int, int, float)}</li> 2522 * <li>{@link #spec(int, Alignment, float)}</li> 2523 * <li>{@link #spec(int, int, Alignment, float)}</li> 2524 * </ul> 2525 * 2526 */ 2527 public static class Spec { 2528 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); 2529 static final float DEFAULT_WEIGHT = 0; 2530 2531 final boolean startDefined; 2532 final Interval span; 2533 final Alignment alignment; 2534 final float weight; 2535 2536 private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { 2537 this.startDefined = startDefined; 2538 this.span = span; 2539 this.alignment = alignment; 2540 this.weight = weight; 2541 } 2542 2543 private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { 2544 this(startDefined, new Interval(start, start + size), alignment, weight); 2545 } 2546 2547 private Alignment getAbsoluteAlignment(boolean horizontal) { 2548 if (alignment != UNDEFINED_ALIGNMENT) { 2549 return alignment; 2550 } 2551 if (weight == 0f) { 2552 return horizontal ? START : BASELINE; 2553 } 2554 return FILL; 2555 } 2556 2557 final Spec copyWriteSpan(Interval span) { 2558 return new Spec(startDefined, span, alignment, weight); 2559 } 2560 2561 final Spec copyWriteAlignment(Alignment alignment) { 2562 return new Spec(startDefined, span, alignment, weight); 2563 } 2564 2565 final int getFlexibility() { 2566 return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; 2567 } 2568 2569 /** 2570 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span} 2571 * properties of this Spec and the supplied parameter are pairwise equal, 2572 * {@code false} otherwise. 2573 * 2574 * @param that the object to compare this spec with 2575 * 2576 * @return {@code true} if the specified object is equal to this 2577 * {@code Spec}; {@code false} otherwise 2578 */ 2579 @Override 2580 public boolean equals(Object that) { 2581 if (this == that) { 2582 return true; 2583 } 2584 if (that == null || getClass() != that.getClass()) { 2585 return false; 2586 } 2587 2588 Spec spec = (Spec) that; 2589 2590 if (!alignment.equals(spec.alignment)) { 2591 return false; 2592 } 2593 //noinspection RedundantIfStatement 2594 if (!span.equals(spec.span)) { 2595 return false; 2596 } 2597 2598 return true; 2599 } 2600 2601 @Override 2602 public int hashCode() { 2603 int result = span.hashCode(); 2604 result = 31 * result + alignment.hashCode(); 2605 return result; 2606 } 2607 } 2608 2609 /** 2610 * Return a Spec, {@code spec}, where: 2611 * <ul> 2612 * <li> {@code spec.span = [start, start + size]} </li> 2613 * <li> {@code spec.alignment = alignment} </li> 2614 * <li> {@code spec.weight = weight} </li> 2615 * </ul> 2616 * <p> 2617 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2618 * 2619 * @param start the start 2620 * @param size the size 2621 * @param alignment the alignment 2622 * @param weight the weight 2623 */ 2624 public static Spec spec(int start, int size, Alignment alignment, float weight) { 2625 return new Spec(start != UNDEFINED, start, size, alignment, weight); 2626 } 2627 2628 /** 2629 * Equivalent to: {@code spec(start, 1, alignment, weight)}. 2630 * 2631 * @param start the start 2632 * @param alignment the alignment 2633 * @param weight the weight 2634 */ 2635 public static Spec spec(int start, Alignment alignment, float weight) { 2636 return spec(start, 1, alignment, weight); 2637 } 2638 2639 /** 2640 * Equivalent to: {@code spec(start, 1, default_alignment, weight)} - 2641 * where {@code default_alignment} is specified in 2642 * {@link android.widget.GridLayout.LayoutParams}. 2643 * 2644 * @param start the start 2645 * @param size the size 2646 * @param weight the weight 2647 */ 2648 public static Spec spec(int start, int size, float weight) { 2649 return spec(start, size, UNDEFINED_ALIGNMENT, weight); 2650 } 2651 2652 /** 2653 * Equivalent to: {@code spec(start, 1, weight)}. 2654 * 2655 * @param start the start 2656 * @param weight the weight 2657 */ 2658 public static Spec spec(int start, float weight) { 2659 return spec(start, 1, weight); 2660 } 2661 2662 /** 2663 * Equivalent to: {@code spec(start, size, alignment, 0f)}. 2664 * 2665 * @param start the start 2666 * @param size the size 2667 * @param alignment the alignment 2668 */ 2669 public static Spec spec(int start, int size, Alignment alignment) { 2670 return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); 2671 } 2672 2673 /** 2674 * Return a Spec, {@code spec}, where: 2675 * <ul> 2676 * <li> {@code spec.span = [start, start + 1]} </li> 2677 * <li> {@code spec.alignment = alignment} </li> 2678 * </ul> 2679 * <p> 2680 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2681 * 2682 * @param start the start index 2683 * @param alignment the alignment 2684 * 2685 * @see #spec(int, int, Alignment) 2686 */ 2687 public static Spec spec(int start, Alignment alignment) { 2688 return spec(start, 1, alignment); 2689 } 2690 2691 /** 2692 * Return a Spec, {@code spec}, where: 2693 * <ul> 2694 * <li> {@code spec.span = [start, start + size]} </li> 2695 * </ul> 2696 * <p> 2697 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2698 * 2699 * @param start the start 2700 * @param size the size 2701 * 2702 * @see #spec(int, Alignment) 2703 */ 2704 public static Spec spec(int start, int size) { 2705 return spec(start, size, UNDEFINED_ALIGNMENT); 2706 } 2707 2708 /** 2709 * Return a Spec, {@code spec}, where: 2710 * <ul> 2711 * <li> {@code spec.span = [start, start + 1]} </li> 2712 * </ul> 2713 * <p> 2714 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2715 * 2716 * @param start the start index 2717 * 2718 * @see #spec(int, int) 2719 */ 2720 public static Spec spec(int start) { 2721 return spec(start, 1); 2722 } 2723 2724 /** 2725 * Alignments specify where a view should be placed within a cell group and 2726 * what size it should be. 2727 * <p> 2728 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec} 2729 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an 2730 * {@code alignment}. Overall placement of the view in the cell 2731 * group is specified by the two alignments which act along each axis independently. 2732 * <p> 2733 * The GridLayout class defines the most common alignments used in general layout: 2734 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START}, 2735 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}. 2736 */ 2737 /* 2738 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, 2739 * to return the appropriate value for the type of alignment being defined. 2740 * The enclosing algorithms position the children 2741 * so that the locations defined by the alignment values 2742 * are the same for all of the views in a group. 2743 * <p> 2744 */ 2745 public static abstract class Alignment { 2746 Alignment() { 2747 } 2748 2749 abstract int getGravityOffset(View view, int cellDelta); 2750 2751 /** 2752 * Returns an alignment value. In the case of vertical alignments the value 2753 * returned should indicate the distance from the top of the view to the 2754 * alignment location. 2755 * For horizontal alignments measurement is made from the left edge of the component. 2756 * 2757 * @param view the view to which this alignment should be applied 2758 * @param viewSize the measured size of the view 2759 * @param mode the basis of alignment: CLIP or OPTICAL 2760 * @return the alignment value 2761 */ 2762 abstract int getAlignmentValue(View view, int viewSize, int mode); 2763 2764 /** 2765 * Returns the size of the view specified by this alignment. 2766 * In the case of vertical alignments this method should return a height; for 2767 * horizontal alignments this method should return the width. 2768 * <p> 2769 * The default implementation returns {@code viewSize}. 2770 * 2771 * @param view the view to which this alignment should be applied 2772 * @param viewSize the measured size of the view 2773 * @param cellSize the size of the cell into which this view will be placed 2774 * @return the aligned size 2775 */ 2776 int getSizeInCell(View view, int viewSize, int cellSize) { 2777 return viewSize; 2778 } 2779 2780 Bounds getBounds() { 2781 return new Bounds(); 2782 } 2783 } 2784 2785 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { 2786 @Override 2787 int getGravityOffset(View view, int cellDelta) { 2788 return UNDEFINED; 2789 } 2790 2791 @Override 2792 public int getAlignmentValue(View view, int viewSize, int mode) { 2793 return UNDEFINED; 2794 } 2795 }; 2796 2797 /** 2798 * Indicates that a view should be aligned with the <em>start</em> 2799 * edges of the other views in its cell group. 2800 */ 2801 private static final Alignment LEADING = new Alignment() { 2802 @Override 2803 int getGravityOffset(View view, int cellDelta) { 2804 return 0; 2805 } 2806 2807 @Override 2808 public int getAlignmentValue(View view, int viewSize, int mode) { 2809 return 0; 2810 } 2811 }; 2812 2813 /** 2814 * Indicates that a view should be aligned with the <em>end</em> 2815 * edges of the other views in its cell group. 2816 */ 2817 private static final Alignment TRAILING = new Alignment() { 2818 @Override 2819 int getGravityOffset(View view, int cellDelta) { 2820 return cellDelta; 2821 } 2822 2823 @Override 2824 public int getAlignmentValue(View view, int viewSize, int mode) { 2825 return viewSize; 2826 } 2827 }; 2828 2829 /** 2830 * Indicates that a view should be aligned with the <em>top</em> 2831 * edges of the other views in its cell group. 2832 */ 2833 public static final Alignment TOP = LEADING; 2834 2835 /** 2836 * Indicates that a view should be aligned with the <em>bottom</em> 2837 * edges of the other views in its cell group. 2838 */ 2839 public static final Alignment BOTTOM = TRAILING; 2840 2841 /** 2842 * Indicates that a view should be aligned with the <em>start</em> 2843 * edges of the other views in its cell group. 2844 */ 2845 public static final Alignment START = LEADING; 2846 2847 /** 2848 * Indicates that a view should be aligned with the <em>end</em> 2849 * edges of the other views in its cell group. 2850 */ 2851 public static final Alignment END = TRAILING; 2852 2853 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) { 2854 return new Alignment() { 2855 @Override 2856 int getGravityOffset(View view, int cellDelta) { 2857 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta); 2858 } 2859 2860 @Override 2861 public int getAlignmentValue(View view, int viewSize, int mode) { 2862 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode); 2863 } 2864 }; 2865 } 2866 2867 /** 2868 * Indicates that a view should be aligned with the <em>left</em> 2869 * edges of the other views in its cell group. 2870 */ 2871 public static final Alignment LEFT = createSwitchingAlignment(START, END); 2872 2873 /** 2874 * Indicates that a view should be aligned with the <em>right</em> 2875 * edges of the other views in its cell group. 2876 */ 2877 public static final Alignment RIGHT = createSwitchingAlignment(END, START); 2878 2879 /** 2880 * Indicates that a view should be <em>centered</em> with the other views in its cell group. 2881 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link 2882 * LayoutParams#columnSpec columnSpecs}. 2883 */ 2884 public static final Alignment CENTER = new Alignment() { 2885 @Override 2886 int getGravityOffset(View view, int cellDelta) { 2887 return cellDelta >> 1; 2888 } 2889 2890 @Override 2891 public int getAlignmentValue(View view, int viewSize, int mode) { 2892 return viewSize >> 1; 2893 } 2894 }; 2895 2896 /** 2897 * Indicates that a view should be aligned with the <em>baselines</em> 2898 * of the other views in its cell group. 2899 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}. 2900 * 2901 * @see View#getBaseline() 2902 */ 2903 public static final Alignment BASELINE = new Alignment() { 2904 @Override 2905 int getGravityOffset(View view, int cellDelta) { 2906 return 0; // baseline gravity is top 2907 } 2908 2909 @Override 2910 public int getAlignmentValue(View view, int viewSize, int mode) { 2911 if (view.getVisibility() == GONE) { 2912 return 0; 2913 } 2914 int baseline = view.getBaseline(); 2915 return baseline == -1 ? UNDEFINED : baseline; 2916 } 2917 2918 @Override 2919 public Bounds getBounds() { 2920 return new Bounds() { 2921 /* 2922 In a baseline aligned row in which some components define a baseline 2923 and some don't, we need a third variable to properly account for all 2924 the sizes. This tracks the maximum size of all the components - 2925 including those that don't define a baseline. 2926 */ 2927 private int size; 2928 2929 @Override 2930 protected void reset() { 2931 super.reset(); 2932 size = Integer.MIN_VALUE; 2933 } 2934 2935 @Override 2936 protected void include(int before, int after) { 2937 super.include(before, after); 2938 size = max(size, before + after); 2939 } 2940 2941 @Override 2942 protected int size(boolean min) { 2943 return max(super.size(min), size); 2944 } 2945 2946 @Override 2947 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) { 2948 return max(0, super.getOffset(gl, c, a, size, hrz)); 2949 } 2950 }; 2951 } 2952 }; 2953 2954 /** 2955 * Indicates that a view should expanded to fit the boundaries of its cell group. 2956 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and 2957 * {@link LayoutParams#columnSpec columnSpecs}. 2958 */ 2959 public static final Alignment FILL = new Alignment() { 2960 @Override 2961 int getGravityOffset(View view, int cellDelta) { 2962 return 0; 2963 } 2964 2965 @Override 2966 public int getAlignmentValue(View view, int viewSize, int mode) { 2967 return UNDEFINED; 2968 } 2969 2970 @Override 2971 public int getSizeInCell(View view, int viewSize, int cellSize) { 2972 return cellSize; 2973 } 2974 }; 2975 2976 static boolean canStretch(int flexibility) { 2977 return (flexibility & CAN_STRETCH) != 0; 2978 } 2979 2980 private static final int INFLEXIBLE = 0; 2981 private static final int CAN_STRETCH = 2; 2982 } 2983