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