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