GridLayout.java revision 7b7578184567f4e4f0740ce935cc192765410cca
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Insets; 24import android.graphics.Paint; 25import android.util.AttributeSet; 26import android.util.Log; 27import android.util.Pair; 28import android.view.Gravity; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.accessibility.AccessibilityEvent; 32import android.view.accessibility.AccessibilityNodeInfo; 33import android.widget.RemoteViews.RemoteView; 34import com.android.internal.R; 35 36import java.lang.reflect.Array; 37import java.util.ArrayList; 38import java.util.Arrays; 39import java.util.HashMap; 40import java.util.List; 41import java.util.Map; 42 43import static android.view.Gravity.*; 44import static android.view.View.MeasureSpec.EXACTLY; 45import static android.view.View.MeasureSpec.makeMeasureSpec; 46import static java.lang.Math.max; 47import 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 151public 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 if (isLayoutRtl()) { 828 int width = getWidth(); 829 graphics.drawLine(width - x1, y1, width - x2, y2, paint); 830 } else { 831 graphics.drawLine(x1, y1, x2, y2, paint); 832 } 833 } 834 835 /** 836 * @hide 837 */ 838 @Override 839 protected void onDebugDrawMargins(Canvas canvas, Paint paint) { 840 // Apply defaults, so as to remove UNDEFINED values 841 LayoutParams lp = new LayoutParams(); 842 for (int i = 0; i < getChildCount(); i++) { 843 View c = getChildAt(i); 844 lp.setMargins( 845 getMargin1(c, true, true), 846 getMargin1(c, false, true), 847 getMargin1(c, true, false), 848 getMargin1(c, false, false)); 849 lp.onDebugDraw(c, canvas, paint); 850 } 851 } 852 853 /** 854 * @hide 855 */ 856 @Override 857 protected void onDebugDraw(Canvas canvas) { 858 Paint paint = new Paint(); 859 paint.setStyle(Paint.Style.STROKE); 860 paint.setColor(Color.argb(50, 255, 255, 255)); 861 862 Insets insets = getOpticalInsets(); 863 864 int top = getPaddingTop() + insets.top; 865 int left = getPaddingLeft() + insets.left; 866 int right = getWidth() - getPaddingRight() - insets.right; 867 int bottom = getHeight() - getPaddingBottom() - insets.bottom; 868 869 int[] xs = horizontalAxis.locations; 870 if (xs != null) { 871 for (int i = 0, length = xs.length; i < length; i++) { 872 int x = left + xs[i]; 873 drawLine(canvas, x, top, x, bottom, paint); 874 } 875 } 876 877 int[] ys = verticalAxis.locations; 878 if (ys != null) { 879 for (int i = 0, length = ys.length; i < length; i++) { 880 int y = top + ys[i]; 881 drawLine(canvas, left, y, right, y, paint); 882 } 883 } 884 885 super.onDebugDraw(canvas); 886 } 887 888 // Add/remove 889 890 /** 891 * @hide 892 */ 893 @Override 894 protected void onViewAdded(View child) { 895 super.onViewAdded(child); 896 invalidateStructure(); 897 } 898 899 /** 900 * @hide 901 */ 902 @Override 903 protected void onViewRemoved(View child) { 904 super.onViewRemoved(child); 905 invalidateStructure(); 906 } 907 908 /** 909 * We need to call invalidateStructure() when a child's GONE flag changes state. 910 * This implementation is a catch-all, invalidating on any change in the visibility flags. 911 * 912 * @hide 913 */ 914 @Override 915 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) { 916 super.onChildVisibilityChanged(child, oldVisibility, newVisibility); 917 if (oldVisibility == GONE || newVisibility == GONE) { 918 invalidateStructure(); 919 } 920 } 921 922 private int computeLayoutParamsHashCode() { 923 int result = 1; 924 for (int i = 0, N = getChildCount(); i < N; i++) { 925 View c = getChildAt(i); 926 if (c.getVisibility() == View.GONE) continue; 927 LayoutParams lp = (LayoutParams) c.getLayoutParams(); 928 result = 31 * result + lp.hashCode(); 929 } 930 return result; 931 } 932 933 private void consistencyCheck() { 934 if (lastLayoutParamsHashCode == UNINITIALIZED_HASH) { 935 validateLayoutParams(); 936 lastLayoutParamsHashCode = computeLayoutParamsHashCode(); 937 } else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) { 938 Log.w(TAG, "The fields of some layout parameters were modified in between layout " + 939 "operations. Check the javadoc for GridLayout.LayoutParams#rowSpec."); 940 invalidateStructure(); 941 consistencyCheck(); 942 } 943 } 944 945 // Measurement 946 947 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, 948 int childWidth, int childHeight) { 949 int childWidthSpec = getChildMeasureSpec(parentWidthSpec, 950 mPaddingLeft + mPaddingRight + getTotalMargin(child, true), childWidth); 951 int childHeightSpec = getChildMeasureSpec(parentHeightSpec, 952 mPaddingTop + mPaddingBottom + getTotalMargin(child, false), childHeight); 953 child.measure(childWidthSpec, childHeightSpec); 954 } 955 956 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) { 957 for (int i = 0, N = getChildCount(); i < N; i++) { 958 View c = getChildAt(i); 959 if (c.getVisibility() == View.GONE) continue; 960 LayoutParams lp = getLayoutParams(c); 961 if (firstPass) { 962 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); 963 } else { 964 boolean horizontal = (orientation == HORIZONTAL); 965 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 966 if (spec.alignment == FILL) { 967 Interval span = spec.span; 968 Axis axis = horizontal ? horizontalAxis : verticalAxis; 969 int[] locations = axis.getLocations(); 970 int cellSize = locations[span.max] - locations[span.min]; 971 int viewSize = cellSize - getTotalMargin(c, horizontal); 972 if (horizontal) { 973 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height); 974 } else { 975 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize); 976 } 977 } 978 } 979 } 980 } 981 982 @Override 983 protected void onMeasure(int widthSpec, int heightSpec) { 984 consistencyCheck(); 985 986 /** If we have been called by {@link View#measure(int, int)}, one of width or height 987 * is likely to have changed. We must invalidate if so. */ 988 invalidateValues(); 989 990 measureChildrenWithMargins(widthSpec, heightSpec, true); 991 992 int width, height; 993 994 // Use the orientation property to decide which axis should be laid out first. 995 if (orientation == HORIZONTAL) { 996 width = horizontalAxis.getMeasure(widthSpec); 997 measureChildrenWithMargins(widthSpec, heightSpec, false); 998 height = verticalAxis.getMeasure(heightSpec); 999 } else { 1000 height = verticalAxis.getMeasure(heightSpec); 1001 measureChildrenWithMargins(widthSpec, heightSpec, false); 1002 width = horizontalAxis.getMeasure(widthSpec); 1003 } 1004 1005 int hPadding = getPaddingLeft() + getPaddingRight(); 1006 int vPadding = getPaddingTop() + getPaddingBottom(); 1007 1008 int measuredWidth = Math.max(hPadding + width, getSuggestedMinimumWidth()); 1009 int measuredHeight = Math.max(vPadding + height, getSuggestedMinimumHeight()); 1010 1011 setMeasuredDimension( 1012 resolveSizeAndState(measuredWidth, widthSpec, 0), 1013 resolveSizeAndState(measuredHeight, heightSpec, 0)); 1014 } 1015 1016 private int getMeasurement(View c, boolean horizontal) { 1017 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); 1018 } 1019 1020 final int getMeasurementIncludingMargin(View c, boolean horizontal) { 1021 if (c.getVisibility() == View.GONE) { 1022 return 0; 1023 } 1024 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal); 1025 } 1026 1027 @Override 1028 public void requestLayout() { 1029 super.requestLayout(); 1030 invalidateValues(); 1031 } 1032 1033 final Alignment getAlignment(Alignment alignment, boolean horizontal) { 1034 return (alignment != UNDEFINED_ALIGNMENT) ? alignment : 1035 (horizontal ? START : BASELINE); 1036 } 1037 1038 // Layout container 1039 1040 /** 1041 * {@inheritDoc} 1042 */ 1043 /* 1044 The layout operation is implemented by delegating the heavy lifting to the 1045 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class. 1046 Together they compute the locations of the vertical and horizontal lines of 1047 the grid (respectively!). 1048 1049 This method is then left with the simpler task of applying margins, gravity 1050 and sizing to each child view and then placing it in its cell. 1051 */ 1052 @Override 1053 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1054 consistencyCheck(); 1055 1056 int targetWidth = right - left; 1057 int targetHeight = bottom - top; 1058 1059 int paddingLeft = getPaddingLeft(); 1060 int paddingTop = getPaddingTop(); 1061 int paddingRight = getPaddingRight(); 1062 int paddingBottom = getPaddingBottom(); 1063 1064 horizontalAxis.layout(targetWidth - paddingLeft - paddingRight); 1065 verticalAxis.layout(targetHeight - paddingTop - paddingBottom); 1066 1067 int[] hLocations = horizontalAxis.getLocations(); 1068 int[] vLocations = verticalAxis.getLocations(); 1069 1070 for (int i = 0, N = getChildCount(); i < N; i++) { 1071 View c = getChildAt(i); 1072 if (c.getVisibility() == View.GONE) continue; 1073 LayoutParams lp = getLayoutParams(c); 1074 Spec columnSpec = lp.columnSpec; 1075 Spec rowSpec = lp.rowSpec; 1076 1077 Interval colSpan = columnSpec.span; 1078 Interval rowSpan = rowSpec.span; 1079 1080 int x1 = hLocations[colSpan.min]; 1081 int y1 = vLocations[rowSpan.min]; 1082 1083 int x2 = hLocations[colSpan.max]; 1084 int y2 = vLocations[rowSpan.max]; 1085 1086 int cellWidth = x2 - x1; 1087 int cellHeight = y2 - y1; 1088 1089 int pWidth = getMeasurement(c, true); 1090 int pHeight = getMeasurement(c, false); 1091 1092 Alignment hAlign = getAlignment(columnSpec.alignment, true); 1093 Alignment vAlign = getAlignment(rowSpec.alignment, false); 1094 1095 Bounds boundsX = horizontalAxis.getGroupBounds().getValue(i); 1096 Bounds boundsY = verticalAxis.getGroupBounds().getValue(i); 1097 1098 // Gravity offsets: the location of the alignment group relative to its cell group. 1099 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true)); 1100 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true)); 1101 1102 int leftMargin = getMargin(c, true, true); 1103 int topMargin = getMargin(c, false, true); 1104 int rightMargin = getMargin(c, true, false); 1105 int bottomMargin = getMargin(c, false, false); 1106 1107 int sumMarginsX = leftMargin + rightMargin; 1108 int sumMarginsY = topMargin + bottomMargin; 1109 1110 // Alignment offsets: the location of the view relative to its alignment group. 1111 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true); 1112 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false); 1113 1114 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX); 1115 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY); 1116 1117 int dx = x1 + gravityOffsetX + alignmentOffsetX; 1118 1119 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx : 1120 targetWidth - width - paddingRight - rightMargin - dx; 1121 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin; 1122 1123 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) { 1124 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); 1125 } 1126 c.layout(cx, cy, cx + width, cy + height); 1127 } 1128 } 1129 1130 @Override 1131 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1132 super.onInitializeAccessibilityEvent(event); 1133 event.setClassName(GridLayout.class.getName()); 1134 } 1135 1136 @Override 1137 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1138 super.onInitializeAccessibilityNodeInfo(info); 1139 info.setClassName(GridLayout.class.getName()); 1140 } 1141 1142 // Inner classes 1143 1144 /* 1145 This internal class houses the algorithm for computing the locations of grid lines; 1146 along either the horizontal or vertical axis. A GridLayout uses two instances of this class - 1147 distinguished by the "horizontal" flag which is true for the horizontal axis and false 1148 for the vertical one. 1149 */ 1150 final class Axis { 1151 private static final int NEW = 0; 1152 private static final int PENDING = 1; 1153 private static final int COMPLETE = 2; 1154 1155 public final boolean horizontal; 1156 1157 public int definedCount = UNDEFINED; 1158 private int maxIndex = UNDEFINED; 1159 1160 PackedMap<Spec, Bounds> groupBounds; 1161 public boolean groupBoundsValid = false; 1162 1163 PackedMap<Interval, MutableInt> forwardLinks; 1164 public boolean forwardLinksValid = false; 1165 1166 PackedMap<Interval, MutableInt> backwardLinks; 1167 public boolean backwardLinksValid = false; 1168 1169 public int[] leadingMargins; 1170 public boolean leadingMarginsValid = false; 1171 1172 public int[] trailingMargins; 1173 public boolean trailingMarginsValid = false; 1174 1175 public Arc[] arcs; 1176 public boolean arcsValid = false; 1177 1178 public int[] locations; 1179 public boolean locationsValid = false; 1180 1181 boolean orderPreserved = DEFAULT_ORDER_PRESERVED; 1182 1183 private MutableInt parentMin = new MutableInt(0); 1184 private MutableInt parentMax = new MutableInt(-MAX_SIZE); 1185 1186 private Axis(boolean horizontal) { 1187 this.horizontal = horizontal; 1188 } 1189 1190 private int calculateMaxIndex() { 1191 // the number Integer.MIN_VALUE + 1 comes up in undefined cells 1192 int result = -1; 1193 for (int i = 0, N = getChildCount(); i < N; i++) { 1194 View c = getChildAt(i); 1195 LayoutParams params = getLayoutParams(c); 1196 Spec spec = horizontal ? params.columnSpec : params.rowSpec; 1197 Interval span = spec.span; 1198 result = max(result, span.min); 1199 result = max(result, span.max); 1200 result = max(result, span.size()); 1201 } 1202 return result == -1 ? UNDEFINED : result; 1203 } 1204 1205 private int getMaxIndex() { 1206 if (maxIndex == UNDEFINED) { 1207 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children 1208 } 1209 return maxIndex; 1210 } 1211 1212 public int getCount() { 1213 return max(definedCount, getMaxIndex()); 1214 } 1215 1216 public void setCount(int count) { 1217 if (count != UNDEFINED && count < getMaxIndex()) { 1218 handleInvalidParams((horizontal ? "column" : "row") + 1219 "Count must be greater than or equal to the maximum of all grid indices " + 1220 "(and spans) defined in the LayoutParams of each child"); 1221 } 1222 this.definedCount = count; 1223 } 1224 1225 public boolean isOrderPreserved() { 1226 return orderPreserved; 1227 } 1228 1229 public void setOrderPreserved(boolean orderPreserved) { 1230 this.orderPreserved = orderPreserved; 1231 invalidateStructure(); 1232 } 1233 1234 private PackedMap<Spec, Bounds> createGroupBounds() { 1235 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class); 1236 for (int i = 0, N = getChildCount(); i < N; i++) { 1237 View c = getChildAt(i); 1238 LayoutParams lp = getLayoutParams(c); 1239 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1240 Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds(); 1241 assoc.put(spec, bounds); 1242 } 1243 return assoc.pack(); 1244 } 1245 1246 private void computeGroupBounds() { 1247 Bounds[] values = groupBounds.values; 1248 for (int i = 0; i < values.length; i++) { 1249 values[i].reset(); 1250 } 1251 for (int i = 0, N = getChildCount(); i < N; i++) { 1252 View c = getChildAt(i); 1253 LayoutParams lp = getLayoutParams(c); 1254 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1255 groupBounds.getValue(i).include(GridLayout.this, c, spec, this); 1256 } 1257 } 1258 1259 public PackedMap<Spec, Bounds> getGroupBounds() { 1260 if (groupBounds == null) { 1261 groupBounds = createGroupBounds(); 1262 } 1263 if (!groupBoundsValid) { 1264 computeGroupBounds(); 1265 groupBoundsValid = true; 1266 } 1267 return groupBounds; 1268 } 1269 1270 // Add values computed by alignment - taking the max of all alignments in each span 1271 private PackedMap<Interval, MutableInt> createLinks(boolean min) { 1272 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class); 1273 Spec[] keys = getGroupBounds().keys; 1274 for (int i = 0, N = keys.length; i < N; i++) { 1275 Interval span = min ? keys[i].span : keys[i].span.inverse(); 1276 result.put(span, new MutableInt()); 1277 } 1278 return result.pack(); 1279 } 1280 1281 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) { 1282 MutableInt[] spans = links.values; 1283 for (int i = 0; i < spans.length; i++) { 1284 spans[i].reset(); 1285 } 1286 1287 // Use getter to trigger a re-evaluation 1288 Bounds[] bounds = getGroupBounds().values; 1289 for (int i = 0; i < bounds.length; i++) { 1290 int size = bounds[i].size(min); 1291 MutableInt valueHolder = links.getValue(i); 1292 // this effectively takes the max() of the minima and the min() of the maxima 1293 valueHolder.value = max(valueHolder.value, min ? size : -size); 1294 } 1295 } 1296 1297 private PackedMap<Interval, MutableInt> getForwardLinks() { 1298 if (forwardLinks == null) { 1299 forwardLinks = createLinks(true); 1300 } 1301 if (!forwardLinksValid) { 1302 computeLinks(forwardLinks, true); 1303 forwardLinksValid = true; 1304 } 1305 return forwardLinks; 1306 } 1307 1308 private PackedMap<Interval, MutableInt> getBackwardLinks() { 1309 if (backwardLinks == null) { 1310 backwardLinks = createLinks(false); 1311 } 1312 if (!backwardLinksValid) { 1313 computeLinks(backwardLinks, false); 1314 backwardLinksValid = true; 1315 } 1316 return backwardLinks; 1317 } 1318 1319 private void include(List<Arc> arcs, Interval key, MutableInt size, 1320 boolean ignoreIfAlreadyPresent) { 1321 /* 1322 Remove self referential links. 1323 These appear: 1324 . as parental constraints when GridLayout has no children 1325 . when components have been marked as GONE 1326 */ 1327 if (key.size() == 0) { 1328 return; 1329 } 1330 // this bit below should really be computed outside here - 1331 // its just to stop default (row/col > 0) constraints obliterating valid entries 1332 if (ignoreIfAlreadyPresent) { 1333 for (Arc arc : arcs) { 1334 Interval span = arc.span; 1335 if (span.equals(key)) { 1336 return; 1337 } 1338 } 1339 } 1340 arcs.add(new Arc(key, size)); 1341 } 1342 1343 private void include(List<Arc> arcs, Interval key, MutableInt size) { 1344 include(arcs, key, size, true); 1345 } 1346 1347 // Group arcs by their first vertex, returning an array of arrays. 1348 // This is linear in the number of arcs. 1349 Arc[][] groupArcsByFirstVertex(Arc[] arcs) { 1350 int N = getCount() + 1; // the number of vertices 1351 Arc[][] result = new Arc[N][]; 1352 int[] sizes = new int[N]; 1353 for (Arc arc : arcs) { 1354 sizes[arc.span.min]++; 1355 } 1356 for (int i = 0; i < sizes.length; i++) { 1357 result[i] = new Arc[sizes[i]]; 1358 } 1359 // reuse the sizes array to hold the current last elements as we insert each arc 1360 Arrays.fill(sizes, 0); 1361 for (Arc arc : arcs) { 1362 int i = arc.span.min; 1363 result[i][sizes[i]++] = arc; 1364 } 1365 1366 return result; 1367 } 1368 1369 private Arc[] topologicalSort(final Arc[] arcs) { 1370 return new Object() { 1371 Arc[] result = new Arc[arcs.length]; 1372 int cursor = result.length - 1; 1373 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); 1374 int[] visited = new int[getCount() + 1]; 1375 1376 void walk(int loc) { 1377 switch (visited[loc]) { 1378 case NEW: { 1379 visited[loc] = PENDING; 1380 for (Arc arc : arcsByVertex[loc]) { 1381 walk(arc.span.max); 1382 result[cursor--] = arc; 1383 } 1384 visited[loc] = COMPLETE; 1385 break; 1386 } 1387 case PENDING: { 1388 // le singe est dans l'arbre 1389 assert false; 1390 break; 1391 } 1392 case COMPLETE: { 1393 break; 1394 } 1395 } 1396 } 1397 1398 Arc[] sort() { 1399 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { 1400 walk(loc); 1401 } 1402 assert cursor == -1; 1403 return result; 1404 } 1405 }.sort(); 1406 } 1407 1408 private Arc[] topologicalSort(List<Arc> arcs) { 1409 return topologicalSort(arcs.toArray(new Arc[arcs.size()])); 1410 } 1411 1412 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) { 1413 for (int i = 0; i < links.keys.length; i++) { 1414 Interval key = links.keys[i]; 1415 include(result, key, links.values[i], false); 1416 } 1417 } 1418 1419 private Arc[] createArcs() { 1420 List<Arc> mins = new ArrayList<Arc>(); 1421 List<Arc> maxs = new ArrayList<Arc>(); 1422 1423 // Add the minimum values from the components. 1424 addComponentSizes(mins, getForwardLinks()); 1425 // Add the maximum values from the components. 1426 addComponentSizes(maxs, getBackwardLinks()); 1427 1428 // Add ordering constraints to prevent row/col sizes from going negative 1429 if (orderPreserved) { 1430 // Add a constraint for every row/col 1431 for (int i = 0; i < getCount(); i++) { 1432 include(mins, new Interval(i, i + 1), new MutableInt(0)); 1433 } 1434 } 1435 1436 // Add the container constraints. Use the version of include that allows 1437 // duplicate entries in case a child spans the entire grid. 1438 int N = getCount(); 1439 include(mins, new Interval(0, N), parentMin, false); 1440 include(maxs, new Interval(N, 0), parentMax, false); 1441 1442 // Sort 1443 Arc[] sMins = topologicalSort(mins); 1444 Arc[] sMaxs = topologicalSort(maxs); 1445 1446 return append(sMins, sMaxs); 1447 } 1448 1449 private void computeArcs() { 1450 // getting the links validates the values that are shared by the arc list 1451 getForwardLinks(); 1452 getBackwardLinks(); 1453 } 1454 1455 public Arc[] getArcs() { 1456 if (arcs == null) { 1457 arcs = createArcs(); 1458 } 1459 if (!arcsValid) { 1460 computeArcs(); 1461 arcsValid = true; 1462 } 1463 return arcs; 1464 } 1465 1466 private boolean relax(int[] locations, Arc entry) { 1467 if (!entry.valid) { 1468 return false; 1469 } 1470 Interval span = entry.span; 1471 int u = span.min; 1472 int v = span.max; 1473 int value = entry.value.value; 1474 int candidate = locations[u] + value; 1475 if (candidate > locations[v]) { 1476 locations[v] = candidate; 1477 return true; 1478 } 1479 return false; 1480 } 1481 1482 private void init(int[] locations) { 1483 Arrays.fill(locations, 0); 1484 } 1485 1486 private String arcsToString(List<Arc> arcs) { 1487 String var = horizontal ? "x" : "y"; 1488 StringBuilder result = new StringBuilder(); 1489 boolean first = true; 1490 for (Arc arc : arcs) { 1491 if (first) { 1492 first = false; 1493 } else { 1494 result = result.append(", "); 1495 } 1496 int src = arc.span.min; 1497 int dst = arc.span.max; 1498 int value = arc.value.value; 1499 result.append((src < dst) ? 1500 var + dst + "-" + var + src + ">=" + value : 1501 var + src + "-" + var + dst + "<=" + -value); 1502 1503 } 1504 return result.toString(); 1505 } 1506 1507 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) { 1508 List<Arc> culprits = new ArrayList<Arc>(); 1509 List<Arc> removed = new ArrayList<Arc>(); 1510 for (int c = 0; c < arcs.length; c++) { 1511 Arc arc = arcs[c]; 1512 if (culprits0[c]) { 1513 culprits.add(arc); 1514 } 1515 if (!arc.valid) { 1516 removed.add(arc); 1517 } 1518 } 1519 Log.d(TAG, axisName + " constraints: " + arcsToString(culprits) + " are inconsistent; " 1520 + "permanently removing: " + arcsToString(removed) + ". "); 1521 } 1522 1523 /* 1524 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N) 1525 1526 GridLayout converts its requirements into a system of linear constraints of the 1527 form: 1528 1529 x[i] - x[j] < a[k] 1530 1531 Where the x[i] are variables and the a[k] are constants. 1532 1533 For example, if the variables were instead labeled x, y, z we might have: 1534 1535 x - y < 17 1536 y - z < 23 1537 z - x < 42 1538 1539 This is a special case of the Linear Programming problem that is, in turn, 1540 equivalent to the single-source shortest paths problem on a digraph, for 1541 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution. 1542 */ 1543 private void solve(Arc[] arcs, int[] locations) { 1544 String axisName = horizontal ? "horizontal" : "vertical"; 1545 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. 1546 boolean[] originalCulprits = null; 1547 1548 for (int p = 0; p < arcs.length; p++) { 1549 init(locations); 1550 1551 // We take one extra pass over traditional Bellman-Ford (and omit their final step) 1552 for (int i = 0; i < N; i++) { 1553 boolean changed = false; 1554 for (int j = 0, length = arcs.length; j < length; j++) { 1555 changed |= relax(locations, arcs[j]); 1556 } 1557 if (!changed) { 1558 if (originalCulprits != null) { 1559 logError(axisName, arcs, originalCulprits); 1560 } 1561 return; 1562 } 1563 } 1564 1565 boolean[] culprits = new boolean[arcs.length]; 1566 for (int i = 0; i < N; i++) { 1567 for (int j = 0, length = arcs.length; j < length; j++) { 1568 culprits[j] |= relax(locations, arcs[j]); 1569 } 1570 } 1571 1572 if (p == 0) { 1573 originalCulprits = culprits; 1574 } 1575 1576 for (int i = 0; i < arcs.length; i++) { 1577 if (culprits[i]) { 1578 Arc arc = arcs[i]; 1579 // Only remove max values, min values alone cannot be inconsistent 1580 if (arc.span.min < arc.span.max) { 1581 continue; 1582 } 1583 arc.valid = false; 1584 break; 1585 } 1586 } 1587 } 1588 } 1589 1590 private void computeMargins(boolean leading) { 1591 int[] margins = leading ? leadingMargins : trailingMargins; 1592 for (int i = 0, N = getChildCount(); i < N; i++) { 1593 View c = getChildAt(i); 1594 if (c.getVisibility() == View.GONE) continue; 1595 LayoutParams lp = getLayoutParams(c); 1596 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1597 Interval span = spec.span; 1598 int index = leading ? span.min : span.max; 1599 margins[index] = max(margins[index], getMargin1(c, horizontal, leading)); 1600 } 1601 } 1602 1603 // External entry points 1604 1605 public int[] getLeadingMargins() { 1606 if (leadingMargins == null) { 1607 leadingMargins = new int[getCount() + 1]; 1608 } 1609 if (!leadingMarginsValid) { 1610 computeMargins(true); 1611 leadingMarginsValid = true; 1612 } 1613 return leadingMargins; 1614 } 1615 1616 public int[] getTrailingMargins() { 1617 if (trailingMargins == null) { 1618 trailingMargins = new int[getCount() + 1]; 1619 } 1620 if (!trailingMarginsValid) { 1621 computeMargins(false); 1622 trailingMarginsValid = true; 1623 } 1624 return trailingMargins; 1625 } 1626 1627 private void computeLocations(int[] a) { 1628 solve(getArcs(), a); 1629 if (!orderPreserved) { 1630 // Solve returns the smallest solution to the constraint system for which all 1631 // values are positive. One value is therefore zero - though if the row/col 1632 // order is not preserved this may not be the first vertex. For consistency, 1633 // translate all the values so that they measure the distance from a[0]; the 1634 // leading edge of the parent. After this transformation some values may be 1635 // negative. 1636 int a0 = a[0]; 1637 for (int i = 0, N = a.length; i < N; i++) { 1638 a[i] = a[i] - a0; 1639 } 1640 } 1641 } 1642 1643 public int[] getLocations() { 1644 if (locations == null) { 1645 int N = getCount() + 1; 1646 locations = new int[N]; 1647 } 1648 if (!locationsValid) { 1649 computeLocations(locations); 1650 locationsValid = true; 1651 } 1652 return locations; 1653 } 1654 1655 private int size(int[] locations) { 1656 // The parental edges are attached to vertices 0 and N - even when order is not 1657 // being preserved and other vertices fall outside this range. Measure the distance 1658 // between vertices 0 and N, assuming that locations[0] = 0. 1659 return locations[getCount()]; 1660 } 1661 1662 private void setParentConstraints(int min, int max) { 1663 parentMin.value = min; 1664 parentMax.value = -max; 1665 locationsValid = false; 1666 } 1667 1668 private int getMeasure(int min, int max) { 1669 setParentConstraints(min, max); 1670 return size(getLocations()); 1671 } 1672 1673 public int getMeasure(int measureSpec) { 1674 int mode = MeasureSpec.getMode(measureSpec); 1675 int size = MeasureSpec.getSize(measureSpec); 1676 switch (mode) { 1677 case MeasureSpec.UNSPECIFIED: { 1678 return getMeasure(0, MAX_SIZE); 1679 } 1680 case MeasureSpec.EXACTLY: { 1681 return getMeasure(size, size); 1682 } 1683 case MeasureSpec.AT_MOST: { 1684 return getMeasure(0, size); 1685 } 1686 default: { 1687 assert false; 1688 return 0; 1689 } 1690 } 1691 } 1692 1693 public void layout(int size) { 1694 setParentConstraints(size, size); 1695 getLocations(); 1696 } 1697 1698 public void invalidateStructure() { 1699 maxIndex = UNDEFINED; 1700 1701 groupBounds = null; 1702 forwardLinks = null; 1703 backwardLinks = null; 1704 1705 leadingMargins = null; 1706 trailingMargins = null; 1707 arcs = null; 1708 1709 locations = null; 1710 1711 invalidateValues(); 1712 } 1713 1714 public void invalidateValues() { 1715 groupBoundsValid = false; 1716 forwardLinksValid = false; 1717 backwardLinksValid = false; 1718 1719 leadingMarginsValid = false; 1720 trailingMarginsValid = false; 1721 arcsValid = false; 1722 1723 locationsValid = false; 1724 } 1725 } 1726 1727 /** 1728 * Layout information associated with each of the children of a GridLayout. 1729 * <p> 1730 * GridLayout supports both row and column spanning and arbitrary forms of alignment within 1731 * each cell group. The fundamental parameters associated with each cell group are 1732 * gathered into their vertical and horizontal components and stored 1733 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters. 1734 * {@link GridLayout.Spec Specs} are immutable structures 1735 * and may be shared between the layout parameters of different children. 1736 * <p> 1737 * The row and column specs contain the leading and trailing indices along each axis 1738 * and together specify the four grid indices that delimit the cells of this cell group. 1739 * <p> 1740 * The alignment properties of the row and column specs together specify 1741 * both aspects of alignment within the cell group. It is also possible to specify a child's 1742 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} 1743 * method. 1744 * 1745 * <h4>WRAP_CONTENT and MATCH_PARENT</h4> 1746 * 1747 * Because the default values of the {@link #width} and {@link #height} 1748 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly 1749 * declared in the layout parameters of GridLayout's children. In addition, 1750 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from 1751 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is 1752 * instead controlled by the principle of <em>flexibility</em>, 1753 * as discussed in {@link GridLayout}. 1754 * 1755 * <h4>Summary</h4> 1756 * 1757 * You should not need to use either of the special size values: 1758 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of 1759 * a GridLayout. 1760 * 1761 * <h4>Default values</h4> 1762 * 1763 * <ul> 1764 * <li>{@link #width} = {@link #WRAP_CONTENT}</li> 1765 * <li>{@link #height} = {@link #WRAP_CONTENT}</li> 1766 * <li>{@link #topMargin} = 0 when 1767 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1768 * {@code false}; otherwise {@link #UNDEFINED}, to 1769 * indicate that a default value should be computed on demand. </li> 1770 * <li>{@link #leftMargin} = 0 when 1771 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1772 * {@code false}; otherwise {@link #UNDEFINED}, to 1773 * indicate that a default value should be computed on demand. </li> 1774 * <li>{@link #bottomMargin} = 0 when 1775 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1776 * {@code false}; otherwise {@link #UNDEFINED}, to 1777 * indicate that a default value should be computed on demand. </li> 1778 * <li>{@link #rightMargin} = 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 #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> 1783 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> 1784 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> 1785 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> 1786 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> 1787 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> 1788 * </ul> 1789 * 1790 * See {@link GridLayout} for a more complete description of the conventions 1791 * used by GridLayout in the interpretation of the properties of this class. 1792 * 1793 * @attr ref android.R.styleable#GridLayout_Layout_layout_row 1794 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan 1795 * @attr ref android.R.styleable#GridLayout_Layout_layout_column 1796 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan 1797 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1798 */ 1799 public static class LayoutParams extends MarginLayoutParams { 1800 1801 // Default values 1802 1803 private static final int DEFAULT_WIDTH = WRAP_CONTENT; 1804 private static final int DEFAULT_HEIGHT = WRAP_CONTENT; 1805 private static final int DEFAULT_MARGIN = UNDEFINED; 1806 private static final int DEFAULT_ROW = UNDEFINED; 1807 private static final int DEFAULT_COLUMN = UNDEFINED; 1808 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); 1809 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); 1810 1811 // TypedArray indices 1812 1813 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin; 1814 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft; 1815 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop; 1816 private static final int RIGHT_MARGIN = 1817 R.styleable.ViewGroup_MarginLayout_layout_marginRight; 1818 private static final int BOTTOM_MARGIN = 1819 R.styleable.ViewGroup_MarginLayout_layout_marginBottom; 1820 1821 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; 1822 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; 1823 1824 private static final int ROW = R.styleable.GridLayout_Layout_layout_row; 1825 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; 1826 1827 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; 1828 1829 // Instance variables 1830 1831 /** 1832 * The spec that defines the vertical characteristics of the cell group 1833 * described by these layout parameters. 1834 * If an assignment is made to this field after a measurement or layout operation 1835 * has already taken place, a call to 1836 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 1837 * must be made to notify GridLayout of the change. GridLayout is normally able 1838 * to detect when code fails to observe this rule, issue a warning and take steps to 1839 * compensate for the omission. This facility is implemented on a best effort basis 1840 * and should not be relied upon in production code - so it is best to include the above 1841 * calls to remove the warnings as soon as it is practical. 1842 */ 1843 public Spec rowSpec = Spec.UNDEFINED; 1844 1845 /** 1846 * The spec that defines the horizontal characteristics of the cell group 1847 * described by these layout parameters. 1848 * If an assignment is made to this field after a measurement or layout operation 1849 * has already taken place, a call to 1850 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 1851 * must be made to notify GridLayout of the change. GridLayout is normally able 1852 * to detect when code fails to observe this rule, issue a warning and take steps to 1853 * compensate for the omission. This facility is implemented on a best effort basis 1854 * and should not be relied upon in production code - so it is best to include the above 1855 * calls to remove the warnings as soon as it is practical. 1856 */ 1857 public Spec columnSpec = Spec.UNDEFINED; 1858 1859 // Constructors 1860 1861 private LayoutParams( 1862 int width, int height, 1863 int left, int top, int right, int bottom, 1864 Spec rowSpec, Spec columnSpec) { 1865 super(width, height); 1866 setMargins(left, top, right, bottom); 1867 this.rowSpec = rowSpec; 1868 this.columnSpec = columnSpec; 1869 } 1870 1871 /** 1872 * Constructs a new LayoutParams instance for this <code>rowSpec</code> 1873 * and <code>columnSpec</code>. All other fields are initialized with 1874 * default values as defined in {@link LayoutParams}. 1875 * 1876 * @param rowSpec the rowSpec 1877 * @param columnSpec the columnSpec 1878 */ 1879 public LayoutParams(Spec rowSpec, Spec columnSpec) { 1880 this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 1881 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 1882 rowSpec, columnSpec); 1883 } 1884 1885 /** 1886 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 1887 */ 1888 public LayoutParams() { 1889 this(Spec.UNDEFINED, Spec.UNDEFINED); 1890 } 1891 1892 // Copying constructors 1893 1894 /** 1895 * {@inheritDoc} 1896 */ 1897 public LayoutParams(ViewGroup.LayoutParams params) { 1898 super(params); 1899 } 1900 1901 /** 1902 * {@inheritDoc} 1903 */ 1904 public LayoutParams(MarginLayoutParams params) { 1905 super(params); 1906 } 1907 1908 /** 1909 * {@inheritDoc} 1910 */ 1911 public LayoutParams(LayoutParams that) { 1912 super(that); 1913 this.rowSpec = that.rowSpec; 1914 this.columnSpec = that.columnSpec; 1915 } 1916 1917 // AttributeSet constructors 1918 1919 /** 1920 * {@inheritDoc} 1921 * 1922 * Values not defined in the attribute set take the default values 1923 * defined in {@link LayoutParams}. 1924 */ 1925 public LayoutParams(Context context, AttributeSet attrs) { 1926 super(context, attrs); 1927 reInitSuper(context, attrs); 1928 init(context, attrs); 1929 } 1930 1931 // Implementation 1932 1933 // Reinitialise the margins using a different default policy than MarginLayoutParams. 1934 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 1935 // so that a layout manager default can be accessed post set up. We need this as, at the 1936 // point of installation, we do not know how many rows/cols there are and therefore 1937 // which elements are positioned next to the container's trailing edges. We need to 1938 // know this as margins around the container's boundary should have different 1939 // defaults to those between peers. 1940 1941 // This method could be parametrized and moved into MarginLayout. 1942 private void reInitSuper(Context context, AttributeSet attrs) { 1943 TypedArray a = 1944 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 1945 try { 1946 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 1947 1948 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 1949 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 1950 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 1951 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 1952 } finally { 1953 a.recycle(); 1954 } 1955 } 1956 1957 private void init(Context context, AttributeSet attrs) { 1958 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); 1959 try { 1960 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); 1961 1962 int column = a.getInt(COLUMN, DEFAULT_COLUMN); 1963 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 1964 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true)); 1965 1966 int row = a.getInt(ROW, DEFAULT_ROW); 1967 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); 1968 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false)); 1969 } finally { 1970 a.recycle(); 1971 } 1972 } 1973 1974 /** 1975 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 1976 * See {@link Gravity}. 1977 * 1978 * @param gravity the new gravity value 1979 * 1980 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1981 */ 1982 public void setGravity(int gravity) { 1983 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); 1984 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); 1985 } 1986 1987 @Override 1988 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 1989 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 1990 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 1991 } 1992 1993 final void setRowSpecSpan(Interval span) { 1994 rowSpec = rowSpec.copyWriteSpan(span); 1995 } 1996 1997 final void setColumnSpecSpan(Interval span) { 1998 columnSpec = columnSpec.copyWriteSpan(span); 1999 } 2000 2001 @Override 2002 public boolean equals(Object o) { 2003 if (this == o) return true; 2004 if (o == null || getClass() != o.getClass()) return false; 2005 2006 LayoutParams that = (LayoutParams) o; 2007 2008 if (!columnSpec.equals(that.columnSpec)) return false; 2009 if (!rowSpec.equals(that.rowSpec)) return false; 2010 2011 return true; 2012 } 2013 2014 @Override 2015 public int hashCode() { 2016 int result = rowSpec.hashCode(); 2017 result = 31 * result + columnSpec.hashCode(); 2018 return result; 2019 } 2020 } 2021 2022 /* 2023 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 2024 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 2025 */ 2026 final static class Arc { 2027 public final Interval span; 2028 public final MutableInt value; 2029 public boolean valid = true; 2030 2031 public Arc(Interval span, MutableInt value) { 2032 this.span = span; 2033 this.value = value; 2034 } 2035 2036 @Override 2037 public String toString() { 2038 return span + " " + (!valid ? "+>" : "->") + " " + value; 2039 } 2040 } 2041 2042 // A mutable Integer - used to avoid heap allocation during the layout operation 2043 2044 final static class MutableInt { 2045 public int value; 2046 2047 public MutableInt() { 2048 reset(); 2049 } 2050 2051 public MutableInt(int value) { 2052 this.value = value; 2053 } 2054 2055 public void reset() { 2056 value = Integer.MIN_VALUE; 2057 } 2058 2059 @Override 2060 public String toString() { 2061 return Integer.toString(value); 2062 } 2063 } 2064 2065 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> { 2066 private final Class<K> keyType; 2067 private final Class<V> valueType; 2068 2069 private Assoc(Class<K> keyType, Class<V> valueType) { 2070 this.keyType = keyType; 2071 this.valueType = valueType; 2072 } 2073 2074 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { 2075 return new Assoc<K, V>(keyType, valueType); 2076 } 2077 2078 public void put(K key, V value) { 2079 add(Pair.create(key, value)); 2080 } 2081 2082 @SuppressWarnings(value = "unchecked") 2083 public PackedMap<K, V> pack() { 2084 int N = size(); 2085 K[] keys = (K[]) Array.newInstance(keyType, N); 2086 V[] values = (V[]) Array.newInstance(valueType, N); 2087 for (int i = 0; i < N; i++) { 2088 keys[i] = get(i).first; 2089 values[i] = get(i).second; 2090 } 2091 return new PackedMap<K, V>(keys, values); 2092 } 2093 } 2094 2095 /* 2096 This data structure is used in place of a Map where we have an index that refers to the order 2097 in which each key/value pairs were added to the map. In this case we store keys and values 2098 in arrays of a length that is equal to the number of unique keys. We also maintain an 2099 array of indexes from insertion order to the compacted arrays of keys and values. 2100 2101 Note that behavior differs from that of a LinkedHashMap in that repeated entries 2102 *do* get added multiples times. So the length of index is equals to the number of 2103 items added. 2104 2105 This is useful in the GridLayout class where we can rely on the order of children not 2106 changing during layout - to use integer-based lookup for our internal structures 2107 rather than using (and storing) an implementation of Map<Key, ?>. 2108 */ 2109 @SuppressWarnings(value = "unchecked") 2110 final static class PackedMap<K, V> { 2111 public final int[] index; 2112 public final K[] keys; 2113 public final V[] values; 2114 2115 private PackedMap(K[] keys, V[] values) { 2116 this.index = createIndex(keys); 2117 2118 this.keys = compact(keys, index); 2119 this.values = compact(values, index); 2120 } 2121 2122 public V getValue(int i) { 2123 return values[index[i]]; 2124 } 2125 2126 private static <K> int[] createIndex(K[] keys) { 2127 int size = keys.length; 2128 int[] result = new int[size]; 2129 2130 Map<K, Integer> keyToIndex = new HashMap<K, Integer>(); 2131 for (int i = 0; i < size; i++) { 2132 K key = keys[i]; 2133 Integer index = keyToIndex.get(key); 2134 if (index == null) { 2135 index = keyToIndex.size(); 2136 keyToIndex.put(key, index); 2137 } 2138 result[i] = index; 2139 } 2140 return result; 2141 } 2142 2143 /* 2144 Create a compact array of keys or values using the supplied index. 2145 */ 2146 private static <K> K[] compact(K[] a, int[] index) { 2147 int size = a.length; 2148 Class<?> componentType = a.getClass().getComponentType(); 2149 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 2150 2151 // this overwrite duplicates, retaining the last equivalent entry 2152 for (int i = 0; i < size; i++) { 2153 result[index[i]] = a[i]; 2154 } 2155 return result; 2156 } 2157 } 2158 2159 /* 2160 For each group (with a given alignment) we need to store the amount of space required 2161 before the alignment point and the amount of space required after it. One side of this 2162 calculation is always 0 for START and END alignments but we don't make use of this. 2163 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 2164 simple optimisations are possible. 2165 2166 The general algorithm therefore is to create a Map (actually a PackedMap) from 2167 group to Bounds and to loop through all Views in the group taking the maximum 2168 of the values for each View. 2169 */ 2170 static class Bounds { 2171 public int before; 2172 public int after; 2173 public int flexibility; // we're flexible iff all included specs are flexible 2174 2175 private Bounds() { 2176 reset(); 2177 } 2178 2179 protected void reset() { 2180 before = Integer.MIN_VALUE; 2181 after = Integer.MIN_VALUE; 2182 flexibility = CAN_STRETCH; // from the above, we're flexible when empty 2183 } 2184 2185 protected void include(int before, int after) { 2186 this.before = max(this.before, before); 2187 this.after = max(this.after, after); 2188 } 2189 2190 protected int size(boolean min) { 2191 if (!min) { 2192 if (canStretch(flexibility)) { 2193 return MAX_SIZE; 2194 } 2195 } 2196 return before + after; 2197 } 2198 2199 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { 2200 return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); 2201 } 2202 2203 protected final void include(GridLayout gl, View c, Spec spec, Axis axis) { 2204 this.flexibility &= spec.getFlexibility(); 2205 boolean horizontal = axis.horizontal; 2206 int size = gl.getMeasurementIncludingMargin(c, horizontal); 2207 Alignment alignment = gl.getAlignment(spec.alignment, horizontal); 2208 // todo test this works correctly when the returned value is UNDEFINED 2209 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); 2210 include(before, size - before); 2211 } 2212 2213 @Override 2214 public String toString() { 2215 return "Bounds{" + 2216 "before=" + before + 2217 ", after=" + after + 2218 '}'; 2219 } 2220 } 2221 2222 /** 2223 * An Interval represents a contiguous range of values that lie between 2224 * the interval's {@link #min} and {@link #max} values. 2225 * <p> 2226 * Intervals are immutable so may be passed as values and used as keys in hash tables. 2227 * It is not necessary to have multiple instances of Intervals which have the same 2228 * {@link #min} and {@link #max} values. 2229 * <p> 2230 * Intervals are often written as {@code [min, max]} and represent the set of values 2231 * {@code x} such that {@code min <= x < max}. 2232 */ 2233 final static class Interval { 2234 /** 2235 * The minimum value. 2236 */ 2237 public final int min; 2238 2239 /** 2240 * The maximum value. 2241 */ 2242 public final int max; 2243 2244 /** 2245 * Construct a new Interval, {@code interval}, where: 2246 * <ul> 2247 * <li> {@code interval.min = min} </li> 2248 * <li> {@code interval.max = max} </li> 2249 * </ul> 2250 * 2251 * @param min the minimum value. 2252 * @param max the maximum value. 2253 */ 2254 public Interval(int min, int max) { 2255 this.min = min; 2256 this.max = max; 2257 } 2258 2259 int size() { 2260 return max - min; 2261 } 2262 2263 Interval inverse() { 2264 return new Interval(max, min); 2265 } 2266 2267 /** 2268 * Returns {@code true} if the {@link #getClass class}, 2269 * {@link #min} and {@link #max} properties of this Interval and the 2270 * supplied parameter are pairwise equal; {@code false} otherwise. 2271 * 2272 * @param that the object to compare this interval with 2273 * 2274 * @return {@code true} if the specified object is equal to this 2275 * {@code Interval}, {@code false} otherwise. 2276 */ 2277 @Override 2278 public boolean equals(Object that) { 2279 if (this == that) { 2280 return true; 2281 } 2282 if (that == null || getClass() != that.getClass()) { 2283 return false; 2284 } 2285 2286 Interval interval = (Interval) that; 2287 2288 if (max != interval.max) { 2289 return false; 2290 } 2291 //noinspection RedundantIfStatement 2292 if (min != interval.min) { 2293 return false; 2294 } 2295 2296 return true; 2297 } 2298 2299 @Override 2300 public int hashCode() { 2301 int result = min; 2302 result = 31 * result + max; 2303 return result; 2304 } 2305 2306 @Override 2307 public String toString() { 2308 return "[" + min + ", " + max + "]"; 2309 } 2310 } 2311 2312 /** 2313 * A Spec defines the horizontal or vertical characteristics of a group of 2314 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em> 2315 * along the appropriate axis. 2316 * <p> 2317 * The <em>grid indices</em> are the leading and trailing edges of this cell group. 2318 * See {@link GridLayout} for a description of the conventions used by GridLayout 2319 * for grid indices. 2320 * <p> 2321 * The <em>alignment</em> property specifies how cells should be aligned in this group. 2322 * For row groups, this specifies the vertical alignment. 2323 * For column groups, this specifies the horizontal alignment. 2324 * <p> 2325 * Use the following static methods to create specs: 2326 * <ul> 2327 * <li>{@link #spec(int)}</li> 2328 * <li>{@link #spec(int, int)}</li> 2329 * <li>{@link #spec(int, Alignment)}</li> 2330 * <li>{@link #spec(int, int, Alignment)}</li> 2331 * </ul> 2332 * 2333 */ 2334 public static class Spec { 2335 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); 2336 2337 final boolean startDefined; 2338 final Interval span; 2339 final Alignment alignment; 2340 2341 private Spec(boolean startDefined, Interval span, Alignment alignment) { 2342 this.startDefined = startDefined; 2343 this.span = span; 2344 this.alignment = alignment; 2345 } 2346 2347 private Spec(boolean startDefined, int start, int size, Alignment alignment) { 2348 this(startDefined, new Interval(start, start + size), alignment); 2349 } 2350 2351 final Spec copyWriteSpan(Interval span) { 2352 return new Spec(startDefined, span, alignment); 2353 } 2354 2355 final Spec copyWriteAlignment(Alignment alignment) { 2356 return new Spec(startDefined, span, alignment); 2357 } 2358 2359 final int getFlexibility() { 2360 return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; 2361 } 2362 2363 /** 2364 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span} 2365 * properties of this Spec and the supplied parameter are pairwise equal, 2366 * {@code false} otherwise. 2367 * 2368 * @param that the object to compare this spec with 2369 * 2370 * @return {@code true} if the specified object is equal to this 2371 * {@code Spec}; {@code false} otherwise 2372 */ 2373 @Override 2374 public boolean equals(Object that) { 2375 if (this == that) { 2376 return true; 2377 } 2378 if (that == null || getClass() != that.getClass()) { 2379 return false; 2380 } 2381 2382 Spec spec = (Spec) that; 2383 2384 if (!alignment.equals(spec.alignment)) { 2385 return false; 2386 } 2387 //noinspection RedundantIfStatement 2388 if (!span.equals(spec.span)) { 2389 return false; 2390 } 2391 2392 return true; 2393 } 2394 2395 @Override 2396 public int hashCode() { 2397 int result = span.hashCode(); 2398 result = 31 * result + alignment.hashCode(); 2399 return result; 2400 } 2401 } 2402 2403 /** 2404 * Return a Spec, {@code spec}, where: 2405 * <ul> 2406 * <li> {@code spec.span = [start, start + size]} </li> 2407 * <li> {@code spec.alignment = alignment} </li> 2408 * </ul> 2409 * <p> 2410 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2411 * 2412 * @param start the start 2413 * @param size the size 2414 * @param alignment the alignment 2415 */ 2416 public static Spec spec(int start, int size, Alignment alignment) { 2417 return new Spec(start != UNDEFINED, start, size, alignment); 2418 } 2419 2420 /** 2421 * Return a Spec, {@code spec}, where: 2422 * <ul> 2423 * <li> {@code spec.span = [start, start + 1]} </li> 2424 * <li> {@code spec.alignment = alignment} </li> 2425 * </ul> 2426 * <p> 2427 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2428 * 2429 * @param start the start index 2430 * @param alignment the alignment 2431 * 2432 * @see #spec(int, int, Alignment) 2433 */ 2434 public static Spec spec(int start, Alignment alignment) { 2435 return spec(start, 1, alignment); 2436 } 2437 2438 /** 2439 * Return a Spec, {@code spec}, where: 2440 * <ul> 2441 * <li> {@code spec.span = [start, start + size]} </li> 2442 * </ul> 2443 * <p> 2444 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2445 * 2446 * @param start the start 2447 * @param size the size 2448 * 2449 * @see #spec(int, Alignment) 2450 */ 2451 public static Spec spec(int start, int size) { 2452 return spec(start, size, UNDEFINED_ALIGNMENT); 2453 } 2454 2455 /** 2456 * Return a Spec, {@code spec}, where: 2457 * <ul> 2458 * <li> {@code spec.span = [start, start + 1]} </li> 2459 * </ul> 2460 * <p> 2461 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2462 * 2463 * @param start the start index 2464 * 2465 * @see #spec(int, int) 2466 */ 2467 public static Spec spec(int start) { 2468 return spec(start, 1); 2469 } 2470 2471 /** 2472 * Alignments specify where a view should be placed within a cell group and 2473 * what size it should be. 2474 * <p> 2475 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec} 2476 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an 2477 * {@code alignment}. Overall placement of the view in the cell 2478 * group is specified by the two alignments which act along each axis independently. 2479 * <p> 2480 * The GridLayout class defines the most common alignments used in general layout: 2481 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START}, 2482 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}. 2483 */ 2484 /* 2485 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, 2486 * to return the appropriate value for the type of alignment being defined. 2487 * The enclosing algorithms position the children 2488 * so that the locations defined by the alignment values 2489 * are the same for all of the views in a group. 2490 * <p> 2491 */ 2492 public static abstract class Alignment { 2493 Alignment() { 2494 } 2495 2496 abstract int getGravityOffset(View view, int cellDelta); 2497 2498 /** 2499 * Returns an alignment value. In the case of vertical alignments the value 2500 * returned should indicate the distance from the top of the view to the 2501 * alignment location. 2502 * For horizontal alignments measurement is made from the left edge of the component. 2503 * 2504 * @param view the view to which this alignment should be applied 2505 * @param viewSize the measured size of the view 2506 * @param mode the basis of alignment: CLIP or OPTICAL 2507 * @return the alignment value 2508 */ 2509 abstract int getAlignmentValue(View view, int viewSize, int mode); 2510 2511 /** 2512 * Returns the size of the view specified by this alignment. 2513 * In the case of vertical alignments this method should return a height; for 2514 * horizontal alignments this method should return the width. 2515 * <p> 2516 * The default implementation returns {@code viewSize}. 2517 * 2518 * @param view the view to which this alignment should be applied 2519 * @param viewSize the measured size of the view 2520 * @param cellSize the size of the cell into which this view will be placed 2521 * @return the aligned size 2522 */ 2523 int getSizeInCell(View view, int viewSize, int cellSize) { 2524 return viewSize; 2525 } 2526 2527 Bounds getBounds() { 2528 return new Bounds(); 2529 } 2530 } 2531 2532 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { 2533 @Override 2534 int getGravityOffset(View view, int cellDelta) { 2535 return UNDEFINED; 2536 } 2537 2538 @Override 2539 public int getAlignmentValue(View view, int viewSize, int mode) { 2540 return UNDEFINED; 2541 } 2542 }; 2543 2544 /** 2545 * Indicates that a view should be aligned with the <em>start</em> 2546 * edges of the other views in its cell group. 2547 */ 2548 private static final Alignment LEADING = new Alignment() { 2549 @Override 2550 int getGravityOffset(View view, int cellDelta) { 2551 return 0; 2552 } 2553 2554 @Override 2555 public int getAlignmentValue(View view, int viewSize, int mode) { 2556 return 0; 2557 } 2558 }; 2559 2560 /** 2561 * Indicates that a view should be aligned with the <em>end</em> 2562 * edges of the other views in its cell group. 2563 */ 2564 private static final Alignment TRAILING = new Alignment() { 2565 @Override 2566 int getGravityOffset(View view, int cellDelta) { 2567 return cellDelta; 2568 } 2569 2570 @Override 2571 public int getAlignmentValue(View view, int viewSize, int mode) { 2572 return viewSize; 2573 } 2574 }; 2575 2576 /** 2577 * Indicates that a view should be aligned with the <em>top</em> 2578 * edges of the other views in its cell group. 2579 */ 2580 public static final Alignment TOP = LEADING; 2581 2582 /** 2583 * Indicates that a view should be aligned with the <em>bottom</em> 2584 * edges of the other views in its cell group. 2585 */ 2586 public static final Alignment BOTTOM = TRAILING; 2587 2588 /** 2589 * Indicates that a view should be aligned with the <em>start</em> 2590 * edges of the other views in its cell group. 2591 */ 2592 public static final Alignment START = LEADING; 2593 2594 /** 2595 * Indicates that a view should be aligned with the <em>end</em> 2596 * edges of the other views in its cell group. 2597 */ 2598 public static final Alignment END = TRAILING; 2599 2600 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) { 2601 return new Alignment() { 2602 @Override 2603 int getGravityOffset(View view, int cellDelta) { 2604 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta); 2605 } 2606 2607 @Override 2608 public int getAlignmentValue(View view, int viewSize, int mode) { 2609 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode); 2610 } 2611 }; 2612 } 2613 2614 /** 2615 * Indicates that a view should be aligned with the <em>left</em> 2616 * edges of the other views in its cell group. 2617 */ 2618 public static final Alignment LEFT = createSwitchingAlignment(START, END); 2619 2620 /** 2621 * Indicates that a view should be aligned with the <em>right</em> 2622 * edges of the other views in its cell group. 2623 */ 2624 public static final Alignment RIGHT = createSwitchingAlignment(END, START); 2625 2626 /** 2627 * Indicates that a view should be <em>centered</em> with the other views in its cell group. 2628 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link 2629 * LayoutParams#columnSpec columnSpecs}. 2630 */ 2631 public static final Alignment CENTER = new Alignment() { 2632 @Override 2633 int getGravityOffset(View view, int cellDelta) { 2634 return cellDelta >> 1; 2635 } 2636 2637 @Override 2638 public int getAlignmentValue(View view, int viewSize, int mode) { 2639 return viewSize >> 1; 2640 } 2641 }; 2642 2643 /** 2644 * Indicates that a view should be aligned with the <em>baselines</em> 2645 * of the other views in its cell group. 2646 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}. 2647 * 2648 * @see View#getBaseline() 2649 */ 2650 public static final Alignment BASELINE = new Alignment() { 2651 @Override 2652 int getGravityOffset(View view, int cellDelta) { 2653 return 0; // baseline gravity is top 2654 } 2655 2656 @Override 2657 public int getAlignmentValue(View view, int viewSize, int mode) { 2658 int baseline = view.getBaseline(); 2659 return baseline == -1 ? UNDEFINED : baseline; 2660 } 2661 2662 @Override 2663 public Bounds getBounds() { 2664 return new Bounds() { 2665 /* 2666 In a baseline aligned row in which some components define a baseline 2667 and some don't, we need a third variable to properly account for all 2668 the sizes. This tracks the maximum size of all the components - 2669 including those that don't define a baseline. 2670 */ 2671 private int size; 2672 2673 @Override 2674 protected void reset() { 2675 super.reset(); 2676 size = Integer.MIN_VALUE; 2677 } 2678 2679 @Override 2680 protected void include(int before, int after) { 2681 super.include(before, after); 2682 size = max(size, before + after); 2683 } 2684 2685 @Override 2686 protected int size(boolean min) { 2687 return max(super.size(min), size); 2688 } 2689 2690 @Override 2691 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) { 2692 return max(0, super.getOffset(gl, c, a, size, hrz)); 2693 } 2694 }; 2695 } 2696 }; 2697 2698 /** 2699 * Indicates that a view should expanded to fit the boundaries of its cell group. 2700 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and 2701 * {@link LayoutParams#columnSpec columnSpecs}. 2702 */ 2703 public static final Alignment FILL = new Alignment() { 2704 @Override 2705 int getGravityOffset(View view, int cellDelta) { 2706 return 0; 2707 } 2708 2709 @Override 2710 public int getAlignmentValue(View view, int viewSize, int mode) { 2711 return UNDEFINED; 2712 } 2713 2714 @Override 2715 public int getSizeInCell(View view, int viewSize, int cellSize) { 2716 return cellSize; 2717 } 2718 }; 2719 2720 static boolean canStretch(int flexibility) { 2721 return (flexibility & CAN_STRETCH) != 0; 2722 } 2723 2724 private static final int INFLEXIBLE = 0; 2725 private static final int CAN_STRETCH = 2; 2726} 2727