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