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