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