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