GridLayout.java revision d05849e8878ab1871465f9433ce38cc3e5f87027
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Insets; 24import android.graphics.Paint; 25import android.util.AttributeSet; 26import android.util.Log; 27import android.util.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.RemoteViews.RemoteView; 36import com.android.internal.R; 37 38import java.lang.reflect.Array; 39import java.util.ArrayList; 40import java.util.Arrays; 41import java.util.HashMap; 42import java.util.List; 43import java.util.Map; 44 45import static android.view.Gravity.*; 46import static android.view.View.MeasureSpec.EXACTLY; 47import static android.view.View.MeasureSpec.makeMeasureSpec; 48import static java.lang.Math.max; 49import static java.lang.Math.min; 50 51/** 52 * A layout that places its children in a rectangular <em>grid</em>. 53 * <p> 54 * The grid is composed of a set of infinitely thin lines that separate the 55 * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced 56 * by grid <em>indices</em>. A grid with {@code N} columns 57 * has {@code N + 1} grid indices that run from {@code 0} 58 * through {@code N} inclusive. Regardless of how GridLayout is 59 * configured, grid index {@code 0} is fixed to the leading edge of the 60 * container and grid index {@code N} is fixed to its trailing edge 61 * (after padding is taken into account). 62 * 63 * <h4>Row and Column Specs</h4> 64 * 65 * Children occupy one or more contiguous cells, as defined 66 * by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and 67 * {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters. 68 * Each spec defines the set of rows or columns that are to be 69 * occupied; and how children should be aligned within the resulting group of cells. 70 * Although cells do not normally overlap in a GridLayout, GridLayout does 71 * not prevent children being defined to occupy the same cell or group of cells. 72 * In this case however, there is no guarantee that children will not themselves 73 * overlap after the layout operation completes. 74 * 75 * <h4>Default Cell Assignment</h4> 76 * 77 * If a child does not specify the row and column indices of the cell it 78 * wishes to occupy, GridLayout assigns cell locations automatically using its: 79 * {@link GridLayout#setOrientation(int) orientation}, 80 * {@link GridLayout#setRowCount(int) rowCount} and 81 * {@link GridLayout#setColumnCount(int) columnCount} properties. 82 * 83 * <h4>Space</h4> 84 * 85 * Space between children may be specified either by using instances of the 86 * dedicated {@link Space} view or by setting the 87 * 88 * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin}, 89 * {@link ViewGroup.MarginLayoutParams#topMargin topMargin}, 90 * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and 91 * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin} 92 * 93 * layout parameters. When the 94 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} 95 * property is set, default margins around children are automatically 96 * allocated based on the prevailing UI style guide for the platform. 97 * Each of the margins so defined may be independently overridden by an assignment 98 * to the appropriate layout parameter. 99 * Default values will generally produce a reasonable spacing between components 100 * but values may change between different releases of the platform. 101 * 102 * <h4>Excess Space Distribution</h4> 103 * 104 * GridLayout's distribution of excess space is based on <em>priority</em> 105 * rather than <em>weight</em>. 106 * <p> 107 * A child's ability to stretch is inferred from the alignment properties of 108 * its row and column groups (which are typically set by setting the 109 * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters). 110 * If alignment was defined along a given axis then the component 111 * is taken as <em>flexible</em> in that direction. If no alignment was set, 112 * the component is instead assumed to be <em>inflexible</em>. 113 * <p> 114 * Multiple components in the same row or column group are 115 * considered to act in <em>parallel</em>. Such a 116 * group is flexible only if <em>all</em> of the components 117 * within it are flexible. Row and column groups that sit either side of a common boundary 118 * are instead considered to act in <em>series</em>. The composite group made of these two 119 * elements is flexible if <em>one</em> of its elements is flexible. 120 * <p> 121 * To make a column stretch, make sure all of the components inside it define a 122 * gravity. To prevent a column from stretching, ensure that one of the components 123 * in the column does not define a gravity. 124 * <p> 125 * When the principle of flexibility does not provide complete disambiguation, 126 * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em> 127 * and <em>bottom</em> edges. 128 * 129 * <h4>Interpretation of GONE</h4> 130 * 131 * For layout purposes, GridLayout treats views whose visibility status is 132 * {@link View#GONE GONE}, as having zero width and height. This is subtly different from 133 * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked 134 * view was alone in a column, that column would itself collapse to zero width if and only if 135 * no gravity was defined on the view. If gravity was defined, then the gone-marked 136 * view has no effect on the layout and the container should be laid out as if the view 137 * had never been added to it. 138 * These statements apply equally to rows as well as columns, and to groups of rows or columns. 139 * 140 * <h5>Limitations</h5> 141 * 142 * GridLayout does not provide support for the principle of <em>weight</em>, as defined in 143 * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible 144 * to configure a GridLayout to distribute excess space between multiple components. 145 * <p> 146 * Some common use-cases may nevertheless be accommodated as follows. 147 * To place equal amounts of space around a component in a cell group; 148 * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}). 149 * For complete control over excess space distribution in a row or column; 150 * use a {@link LinearLayout} subview to hold the components in the associated cell group. 151 * When using either of these techniques, bear in mind that cell groups may be defined to overlap. 152 * <p> 153 * See {@link GridLayout.LayoutParams} for a full description of the 154 * layout parameters used by GridLayout. 155 * 156 * @attr ref android.R.styleable#GridLayout_orientation 157 * @attr ref android.R.styleable#GridLayout_rowCount 158 * @attr ref android.R.styleable#GridLayout_columnCount 159 * @attr ref android.R.styleable#GridLayout_useDefaultMargins 160 * @attr ref android.R.styleable#GridLayout_rowOrderPreserved 161 * @attr ref android.R.styleable#GridLayout_columnOrderPreserved 162 */ 163@RemoteView 164public class GridLayout extends 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 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() == 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.ViewGroup_MarginLayout_layout_margin; 1869 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft; 1870 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop; 1871 private static final int RIGHT_MARGIN = 1872 R.styleable.ViewGroup_MarginLayout_layout_marginRight; 1873 private static final int BOTTOM_MARGIN = 1874 R.styleable.ViewGroup_MarginLayout_layout_marginBottom; 1875 1876 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; 1877 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; 1878 1879 private static final int ROW = R.styleable.GridLayout_Layout_layout_row; 1880 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; 1881 1882 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; 1883 1884 // Instance variables 1885 1886 /** 1887 * The spec that defines the vertical characteristics of the cell group 1888 * described by these layout parameters. 1889 * If an assignment is made to this field after a measurement or layout operation 1890 * has already taken place, a call to 1891 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 1892 * must be made to notify GridLayout of the change. GridLayout is normally able 1893 * to detect when code fails to observe this rule, issue a warning and take steps to 1894 * compensate for the omission. This facility is implemented on a best effort basis 1895 * and should not be relied upon in production code - so it is best to include the above 1896 * calls to remove the warnings as soon as it is practical. 1897 */ 1898 public Spec rowSpec = Spec.UNDEFINED; 1899 1900 /** 1901 * The spec that defines the horizontal characteristics of the cell group 1902 * described by these layout parameters. 1903 * If an assignment is made to this field after a measurement or layout operation 1904 * has already taken place, a call to 1905 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 1906 * must be made to notify GridLayout of the change. GridLayout is normally able 1907 * to detect when code fails to observe this rule, issue a warning and take steps to 1908 * compensate for the omission. This facility is implemented on a best effort basis 1909 * and should not be relied upon in production code - so it is best to include the above 1910 * calls to remove the warnings as soon as it is practical. 1911 */ 1912 public Spec columnSpec = Spec.UNDEFINED; 1913 1914 // Constructors 1915 1916 private LayoutParams( 1917 int width, int height, 1918 int left, int top, int right, int bottom, 1919 Spec rowSpec, Spec columnSpec) { 1920 super(width, height); 1921 setMargins(left, top, right, bottom); 1922 this.rowSpec = rowSpec; 1923 this.columnSpec = columnSpec; 1924 } 1925 1926 /** 1927 * Constructs a new LayoutParams instance for this <code>rowSpec</code> 1928 * and <code>columnSpec</code>. All other fields are initialized with 1929 * default values as defined in {@link LayoutParams}. 1930 * 1931 * @param rowSpec the rowSpec 1932 * @param columnSpec the columnSpec 1933 */ 1934 public LayoutParams(Spec rowSpec, Spec columnSpec) { 1935 this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 1936 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 1937 rowSpec, columnSpec); 1938 } 1939 1940 /** 1941 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 1942 */ 1943 public LayoutParams() { 1944 this(Spec.UNDEFINED, Spec.UNDEFINED); 1945 } 1946 1947 // Copying constructors 1948 1949 /** 1950 * {@inheritDoc} 1951 */ 1952 public LayoutParams(ViewGroup.LayoutParams params) { 1953 super(params); 1954 } 1955 1956 /** 1957 * {@inheritDoc} 1958 */ 1959 public LayoutParams(MarginLayoutParams params) { 1960 super(params); 1961 } 1962 1963 /** 1964 * {@inheritDoc} 1965 */ 1966 public LayoutParams(LayoutParams that) { 1967 super(that); 1968 this.rowSpec = that.rowSpec; 1969 this.columnSpec = that.columnSpec; 1970 } 1971 1972 // AttributeSet constructors 1973 1974 /** 1975 * {@inheritDoc} 1976 * 1977 * Values not defined in the attribute set take the default values 1978 * defined in {@link LayoutParams}. 1979 */ 1980 public LayoutParams(Context context, AttributeSet attrs) { 1981 super(context, attrs); 1982 reInitSuper(context, attrs); 1983 init(context, attrs); 1984 } 1985 1986 // Implementation 1987 1988 // Reinitialise the margins using a different default policy than MarginLayoutParams. 1989 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 1990 // so that a layout manager default can be accessed post set up. We need this as, at the 1991 // point of installation, we do not know how many rows/cols there are and therefore 1992 // which elements are positioned next to the container's trailing edges. We need to 1993 // know this as margins around the container's boundary should have different 1994 // defaults to those between peers. 1995 1996 // This method could be parametrized and moved into MarginLayout. 1997 private void reInitSuper(Context context, AttributeSet attrs) { 1998 TypedArray a = 1999 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 2000 try { 2001 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 2002 2003 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 2004 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 2005 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 2006 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 2007 } finally { 2008 a.recycle(); 2009 } 2010 } 2011 2012 private void init(Context context, AttributeSet attrs) { 2013 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); 2014 try { 2015 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); 2016 2017 int column = a.getInt(COLUMN, DEFAULT_COLUMN); 2018 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 2019 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true)); 2020 2021 int row = a.getInt(ROW, DEFAULT_ROW); 2022 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); 2023 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false)); 2024 } finally { 2025 a.recycle(); 2026 } 2027 } 2028 2029 /** 2030 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 2031 * See {@link Gravity}. 2032 * 2033 * @param gravity the new gravity value 2034 * 2035 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 2036 */ 2037 public void setGravity(int gravity) { 2038 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); 2039 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); 2040 } 2041 2042 @Override 2043 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 2044 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 2045 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 2046 } 2047 2048 final void setRowSpecSpan(Interval span) { 2049 rowSpec = rowSpec.copyWriteSpan(span); 2050 } 2051 2052 final void setColumnSpecSpan(Interval span) { 2053 columnSpec = columnSpec.copyWriteSpan(span); 2054 } 2055 2056 @Override 2057 public boolean equals(Object o) { 2058 if (this == o) return true; 2059 if (o == null || getClass() != o.getClass()) return false; 2060 2061 LayoutParams that = (LayoutParams) o; 2062 2063 if (!columnSpec.equals(that.columnSpec)) return false; 2064 if (!rowSpec.equals(that.rowSpec)) return false; 2065 2066 return true; 2067 } 2068 2069 @Override 2070 public int hashCode() { 2071 int result = rowSpec.hashCode(); 2072 result = 31 * result + columnSpec.hashCode(); 2073 return result; 2074 } 2075 } 2076 2077 /* 2078 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 2079 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 2080 */ 2081 final static class Arc { 2082 public final Interval span; 2083 public final MutableInt value; 2084 public boolean valid = true; 2085 2086 public Arc(Interval span, MutableInt value) { 2087 this.span = span; 2088 this.value = value; 2089 } 2090 2091 @Override 2092 public String toString() { 2093 return span + " " + (!valid ? "+>" : "->") + " " + value; 2094 } 2095 } 2096 2097 // A mutable Integer - used to avoid heap allocation during the layout operation 2098 2099 final static class MutableInt { 2100 public int value; 2101 2102 public MutableInt() { 2103 reset(); 2104 } 2105 2106 public MutableInt(int value) { 2107 this.value = value; 2108 } 2109 2110 public void reset() { 2111 value = Integer.MIN_VALUE; 2112 } 2113 2114 @Override 2115 public String toString() { 2116 return Integer.toString(value); 2117 } 2118 } 2119 2120 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> { 2121 private final Class<K> keyType; 2122 private final Class<V> valueType; 2123 2124 private Assoc(Class<K> keyType, Class<V> valueType) { 2125 this.keyType = keyType; 2126 this.valueType = valueType; 2127 } 2128 2129 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { 2130 return new Assoc<K, V>(keyType, valueType); 2131 } 2132 2133 public void put(K key, V value) { 2134 add(Pair.create(key, value)); 2135 } 2136 2137 @SuppressWarnings(value = "unchecked") 2138 public PackedMap<K, V> pack() { 2139 int N = size(); 2140 K[] keys = (K[]) Array.newInstance(keyType, N); 2141 V[] values = (V[]) Array.newInstance(valueType, N); 2142 for (int i = 0; i < N; i++) { 2143 keys[i] = get(i).first; 2144 values[i] = get(i).second; 2145 } 2146 return new PackedMap<K, V>(keys, values); 2147 } 2148 } 2149 2150 /* 2151 This data structure is used in place of a Map where we have an index that refers to the order 2152 in which each key/value pairs were added to the map. In this case we store keys and values 2153 in arrays of a length that is equal to the number of unique keys. We also maintain an 2154 array of indexes from insertion order to the compacted arrays of keys and values. 2155 2156 Note that behavior differs from that of a LinkedHashMap in that repeated entries 2157 *do* get added multiples times. So the length of index is equals to the number of 2158 items added. 2159 2160 This is useful in the GridLayout class where we can rely on the order of children not 2161 changing during layout - to use integer-based lookup for our internal structures 2162 rather than using (and storing) an implementation of Map<Key, ?>. 2163 */ 2164 @SuppressWarnings(value = "unchecked") 2165 final static class PackedMap<K, V> { 2166 public final int[] index; 2167 public final K[] keys; 2168 public final V[] values; 2169 2170 private PackedMap(K[] keys, V[] values) { 2171 this.index = createIndex(keys); 2172 2173 this.keys = compact(keys, index); 2174 this.values = compact(values, index); 2175 } 2176 2177 public V getValue(int i) { 2178 return values[index[i]]; 2179 } 2180 2181 private static <K> int[] createIndex(K[] keys) { 2182 int size = keys.length; 2183 int[] result = new int[size]; 2184 2185 Map<K, Integer> keyToIndex = new HashMap<K, Integer>(); 2186 for (int i = 0; i < size; i++) { 2187 K key = keys[i]; 2188 Integer index = keyToIndex.get(key); 2189 if (index == null) { 2190 index = keyToIndex.size(); 2191 keyToIndex.put(key, index); 2192 } 2193 result[i] = index; 2194 } 2195 return result; 2196 } 2197 2198 /* 2199 Create a compact array of keys or values using the supplied index. 2200 */ 2201 private static <K> K[] compact(K[] a, int[] index) { 2202 int size = a.length; 2203 Class<?> componentType = a.getClass().getComponentType(); 2204 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 2205 2206 // this overwrite duplicates, retaining the last equivalent entry 2207 for (int i = 0; i < size; i++) { 2208 result[index[i]] = a[i]; 2209 } 2210 return result; 2211 } 2212 } 2213 2214 /* 2215 For each group (with a given alignment) we need to store the amount of space required 2216 before the alignment point and the amount of space required after it. One side of this 2217 calculation is always 0 for START and END alignments but we don't make use of this. 2218 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 2219 simple optimisations are possible. 2220 2221 The general algorithm therefore is to create a Map (actually a PackedMap) from 2222 group to Bounds and to loop through all Views in the group taking the maximum 2223 of the values for each View. 2224 */ 2225 static class Bounds { 2226 public int before; 2227 public int after; 2228 public int flexibility; // we're flexible iff all included specs are flexible 2229 2230 private Bounds() { 2231 reset(); 2232 } 2233 2234 protected void reset() { 2235 before = Integer.MIN_VALUE; 2236 after = Integer.MIN_VALUE; 2237 flexibility = CAN_STRETCH; // from the above, we're flexible when empty 2238 } 2239 2240 protected void include(int before, int after) { 2241 this.before = max(this.before, before); 2242 this.after = max(this.after, after); 2243 } 2244 2245 protected int size(boolean min) { 2246 if (!min) { 2247 if (canStretch(flexibility)) { 2248 return MAX_SIZE; 2249 } 2250 } 2251 return before + after; 2252 } 2253 2254 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { 2255 return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); 2256 } 2257 2258 protected final void include(GridLayout gl, View c, Spec spec, Axis axis) { 2259 this.flexibility &= spec.getFlexibility(); 2260 boolean horizontal = axis.horizontal; 2261 int size = gl.getMeasurementIncludingMargin(c, horizontal); 2262 Alignment alignment = gl.getAlignment(spec.alignment, horizontal); 2263 // todo test this works correctly when the returned value is UNDEFINED 2264 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); 2265 include(before, size - before); 2266 } 2267 2268 @Override 2269 public String toString() { 2270 return "Bounds{" + 2271 "before=" + before + 2272 ", after=" + after + 2273 '}'; 2274 } 2275 } 2276 2277 /** 2278 * An Interval represents a contiguous range of values that lie between 2279 * the interval's {@link #min} and {@link #max} values. 2280 * <p> 2281 * Intervals are immutable so may be passed as values and used as keys in hash tables. 2282 * It is not necessary to have multiple instances of Intervals which have the same 2283 * {@link #min} and {@link #max} values. 2284 * <p> 2285 * Intervals are often written as {@code [min, max]} and represent the set of values 2286 * {@code x} such that {@code min <= x < max}. 2287 */ 2288 final static class Interval { 2289 /** 2290 * The minimum value. 2291 */ 2292 public final int min; 2293 2294 /** 2295 * The maximum value. 2296 */ 2297 public final int max; 2298 2299 /** 2300 * Construct a new Interval, {@code interval}, where: 2301 * <ul> 2302 * <li> {@code interval.min = min} </li> 2303 * <li> {@code interval.max = max} </li> 2304 * </ul> 2305 * 2306 * @param min the minimum value. 2307 * @param max the maximum value. 2308 */ 2309 public Interval(int min, int max) { 2310 this.min = min; 2311 this.max = max; 2312 } 2313 2314 int size() { 2315 return max - min; 2316 } 2317 2318 Interval inverse() { 2319 return new Interval(max, min); 2320 } 2321 2322 /** 2323 * Returns {@code true} if the {@link #getClass class}, 2324 * {@link #min} and {@link #max} properties of this Interval and the 2325 * supplied parameter are pairwise equal; {@code false} otherwise. 2326 * 2327 * @param that the object to compare this interval with 2328 * 2329 * @return {@code true} if the specified object is equal to this 2330 * {@code Interval}, {@code false} otherwise. 2331 */ 2332 @Override 2333 public boolean equals(Object that) { 2334 if (this == that) { 2335 return true; 2336 } 2337 if (that == null || getClass() != that.getClass()) { 2338 return false; 2339 } 2340 2341 Interval interval = (Interval) that; 2342 2343 if (max != interval.max) { 2344 return false; 2345 } 2346 //noinspection RedundantIfStatement 2347 if (min != interval.min) { 2348 return false; 2349 } 2350 2351 return true; 2352 } 2353 2354 @Override 2355 public int hashCode() { 2356 int result = min; 2357 result = 31 * result + max; 2358 return result; 2359 } 2360 2361 @Override 2362 public String toString() { 2363 return "[" + min + ", " + max + "]"; 2364 } 2365 } 2366 2367 /** 2368 * A Spec defines the horizontal or vertical characteristics of a group of 2369 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em> 2370 * along the appropriate axis. 2371 * <p> 2372 * The <em>grid indices</em> are the leading and trailing edges of this cell group. 2373 * See {@link GridLayout} for a description of the conventions used by GridLayout 2374 * for grid indices. 2375 * <p> 2376 * The <em>alignment</em> property specifies how cells should be aligned in this group. 2377 * For row groups, this specifies the vertical alignment. 2378 * For column groups, this specifies the horizontal alignment. 2379 * <p> 2380 * Use the following static methods to create specs: 2381 * <ul> 2382 * <li>{@link #spec(int)}</li> 2383 * <li>{@link #spec(int, int)}</li> 2384 * <li>{@link #spec(int, Alignment)}</li> 2385 * <li>{@link #spec(int, int, Alignment)}</li> 2386 * </ul> 2387 * 2388 */ 2389 public static class Spec { 2390 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); 2391 2392 final boolean startDefined; 2393 final Interval span; 2394 final Alignment alignment; 2395 2396 private Spec(boolean startDefined, Interval span, Alignment alignment) { 2397 this.startDefined = startDefined; 2398 this.span = span; 2399 this.alignment = alignment; 2400 } 2401 2402 private Spec(boolean startDefined, int start, int size, Alignment alignment) { 2403 this(startDefined, new Interval(start, start + size), alignment); 2404 } 2405 2406 final Spec copyWriteSpan(Interval span) { 2407 return new Spec(startDefined, span, alignment); 2408 } 2409 2410 final Spec copyWriteAlignment(Alignment alignment) { 2411 return new Spec(startDefined, span, alignment); 2412 } 2413 2414 final int getFlexibility() { 2415 return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; 2416 } 2417 2418 /** 2419 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span} 2420 * properties of this Spec and the supplied parameter are pairwise equal, 2421 * {@code false} otherwise. 2422 * 2423 * @param that the object to compare this spec with 2424 * 2425 * @return {@code true} if the specified object is equal to this 2426 * {@code Spec}; {@code false} otherwise 2427 */ 2428 @Override 2429 public boolean equals(Object that) { 2430 if (this == that) { 2431 return true; 2432 } 2433 if (that == null || getClass() != that.getClass()) { 2434 return false; 2435 } 2436 2437 Spec spec = (Spec) that; 2438 2439 if (!alignment.equals(spec.alignment)) { 2440 return false; 2441 } 2442 //noinspection RedundantIfStatement 2443 if (!span.equals(spec.span)) { 2444 return false; 2445 } 2446 2447 return true; 2448 } 2449 2450 @Override 2451 public int hashCode() { 2452 int result = span.hashCode(); 2453 result = 31 * result + alignment.hashCode(); 2454 return result; 2455 } 2456 } 2457 2458 /** 2459 * Return a Spec, {@code spec}, where: 2460 * <ul> 2461 * <li> {@code spec.span = [start, start + size]} </li> 2462 * <li> {@code spec.alignment = alignment} </li> 2463 * </ul> 2464 * <p> 2465 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2466 * 2467 * @param start the start 2468 * @param size the size 2469 * @param alignment the alignment 2470 */ 2471 public static Spec spec(int start, int size, Alignment alignment) { 2472 return new Spec(start != UNDEFINED, start, size, alignment); 2473 } 2474 2475 /** 2476 * Return a Spec, {@code spec}, where: 2477 * <ul> 2478 * <li> {@code spec.span = [start, start + 1]} </li> 2479 * <li> {@code spec.alignment = alignment} </li> 2480 * </ul> 2481 * <p> 2482 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2483 * 2484 * @param start the start index 2485 * @param alignment the alignment 2486 * 2487 * @see #spec(int, int, Alignment) 2488 */ 2489 public static Spec spec(int start, Alignment alignment) { 2490 return spec(start, 1, alignment); 2491 } 2492 2493 /** 2494 * Return a Spec, {@code spec}, where: 2495 * <ul> 2496 * <li> {@code spec.span = [start, start + size]} </li> 2497 * </ul> 2498 * <p> 2499 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2500 * 2501 * @param start the start 2502 * @param size the size 2503 * 2504 * @see #spec(int, Alignment) 2505 */ 2506 public static Spec spec(int start, int size) { 2507 return spec(start, size, UNDEFINED_ALIGNMENT); 2508 } 2509 2510 /** 2511 * Return a Spec, {@code spec}, where: 2512 * <ul> 2513 * <li> {@code spec.span = [start, start + 1]} </li> 2514 * </ul> 2515 * <p> 2516 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2517 * 2518 * @param start the start index 2519 * 2520 * @see #spec(int, int) 2521 */ 2522 public static Spec spec(int start) { 2523 return spec(start, 1); 2524 } 2525 2526 /** 2527 * Alignments specify where a view should be placed within a cell group and 2528 * what size it should be. 2529 * <p> 2530 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec} 2531 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an 2532 * {@code alignment}. Overall placement of the view in the cell 2533 * group is specified by the two alignments which act along each axis independently. 2534 * <p> 2535 * The GridLayout class defines the most common alignments used in general layout: 2536 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START}, 2537 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}. 2538 */ 2539 /* 2540 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, 2541 * to return the appropriate value for the type of alignment being defined. 2542 * The enclosing algorithms position the children 2543 * so that the locations defined by the alignment values 2544 * are the same for all of the views in a group. 2545 * <p> 2546 */ 2547 public static abstract class Alignment { 2548 Alignment() { 2549 } 2550 2551 abstract int getGravityOffset(View view, int cellDelta); 2552 2553 /** 2554 * Returns an alignment value. In the case of vertical alignments the value 2555 * returned should indicate the distance from the top of the view to the 2556 * alignment location. 2557 * For horizontal alignments measurement is made from the left edge of the component. 2558 * 2559 * @param view the view to which this alignment should be applied 2560 * @param viewSize the measured size of the view 2561 * @param mode the basis of alignment: CLIP or OPTICAL 2562 * @return the alignment value 2563 */ 2564 abstract int getAlignmentValue(View view, int viewSize, int mode); 2565 2566 /** 2567 * Returns the size of the view specified by this alignment. 2568 * In the case of vertical alignments this method should return a height; for 2569 * horizontal alignments this method should return the width. 2570 * <p> 2571 * The default implementation returns {@code viewSize}. 2572 * 2573 * @param view the view to which this alignment should be applied 2574 * @param viewSize the measured size of the view 2575 * @param cellSize the size of the cell into which this view will be placed 2576 * @return the aligned size 2577 */ 2578 int getSizeInCell(View view, int viewSize, int cellSize) { 2579 return viewSize; 2580 } 2581 2582 Bounds getBounds() { 2583 return new Bounds(); 2584 } 2585 } 2586 2587 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { 2588 @Override 2589 int getGravityOffset(View view, int cellDelta) { 2590 return UNDEFINED; 2591 } 2592 2593 @Override 2594 public int getAlignmentValue(View view, int viewSize, int mode) { 2595 return UNDEFINED; 2596 } 2597 }; 2598 2599 /** 2600 * Indicates that a view should be aligned with the <em>start</em> 2601 * edges of the other views in its cell group. 2602 */ 2603 private static final Alignment LEADING = new Alignment() { 2604 @Override 2605 int getGravityOffset(View view, int cellDelta) { 2606 return 0; 2607 } 2608 2609 @Override 2610 public int getAlignmentValue(View view, int viewSize, int mode) { 2611 return 0; 2612 } 2613 }; 2614 2615 /** 2616 * Indicates that a view should be aligned with the <em>end</em> 2617 * edges of the other views in its cell group. 2618 */ 2619 private static final Alignment TRAILING = new Alignment() { 2620 @Override 2621 int getGravityOffset(View view, int cellDelta) { 2622 return cellDelta; 2623 } 2624 2625 @Override 2626 public int getAlignmentValue(View view, int viewSize, int mode) { 2627 return viewSize; 2628 } 2629 }; 2630 2631 /** 2632 * Indicates that a view should be aligned with the <em>top</em> 2633 * edges of the other views in its cell group. 2634 */ 2635 public static final Alignment TOP = LEADING; 2636 2637 /** 2638 * Indicates that a view should be aligned with the <em>bottom</em> 2639 * edges of the other views in its cell group. 2640 */ 2641 public static final Alignment BOTTOM = TRAILING; 2642 2643 /** 2644 * Indicates that a view should be aligned with the <em>start</em> 2645 * edges of the other views in its cell group. 2646 */ 2647 public static final Alignment START = LEADING; 2648 2649 /** 2650 * Indicates that a view should be aligned with the <em>end</em> 2651 * edges of the other views in its cell group. 2652 */ 2653 public static final Alignment END = TRAILING; 2654 2655 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) { 2656 return new Alignment() { 2657 @Override 2658 int getGravityOffset(View view, int cellDelta) { 2659 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta); 2660 } 2661 2662 @Override 2663 public int getAlignmentValue(View view, int viewSize, int mode) { 2664 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode); 2665 } 2666 }; 2667 } 2668 2669 /** 2670 * Indicates that a view should be aligned with the <em>left</em> 2671 * edges of the other views in its cell group. 2672 */ 2673 public static final Alignment LEFT = createSwitchingAlignment(START, END); 2674 2675 /** 2676 * Indicates that a view should be aligned with the <em>right</em> 2677 * edges of the other views in its cell group. 2678 */ 2679 public static final Alignment RIGHT = createSwitchingAlignment(END, START); 2680 2681 /** 2682 * Indicates that a view should be <em>centered</em> with the other views in its cell group. 2683 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link 2684 * LayoutParams#columnSpec columnSpecs}. 2685 */ 2686 public static final Alignment CENTER = new Alignment() { 2687 @Override 2688 int getGravityOffset(View view, int cellDelta) { 2689 return cellDelta >> 1; 2690 } 2691 2692 @Override 2693 public int getAlignmentValue(View view, int viewSize, int mode) { 2694 return viewSize >> 1; 2695 } 2696 }; 2697 2698 /** 2699 * Indicates that a view should be aligned with the <em>baselines</em> 2700 * of the other views in its cell group. 2701 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}. 2702 * 2703 * @see View#getBaseline() 2704 */ 2705 public static final Alignment BASELINE = new Alignment() { 2706 @Override 2707 int getGravityOffset(View view, int cellDelta) { 2708 return 0; // baseline gravity is top 2709 } 2710 2711 @Override 2712 public int getAlignmentValue(View view, int viewSize, int mode) { 2713 if (view.getVisibility() == GONE) { 2714 return 0; 2715 } 2716 int baseline = view.getBaseline(); 2717 return baseline == -1 ? UNDEFINED : baseline; 2718 } 2719 2720 @Override 2721 public Bounds getBounds() { 2722 return new Bounds() { 2723 /* 2724 In a baseline aligned row in which some components define a baseline 2725 and some don't, we need a third variable to properly account for all 2726 the sizes. This tracks the maximum size of all the components - 2727 including those that don't define a baseline. 2728 */ 2729 private int size; 2730 2731 @Override 2732 protected void reset() { 2733 super.reset(); 2734 size = Integer.MIN_VALUE; 2735 } 2736 2737 @Override 2738 protected void include(int before, int after) { 2739 super.include(before, after); 2740 size = max(size, before + after); 2741 } 2742 2743 @Override 2744 protected int size(boolean min) { 2745 return max(super.size(min), size); 2746 } 2747 2748 @Override 2749 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) { 2750 return max(0, super.getOffset(gl, c, a, size, hrz)); 2751 } 2752 }; 2753 } 2754 }; 2755 2756 /** 2757 * Indicates that a view should expanded to fit the boundaries of its cell group. 2758 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and 2759 * {@link LayoutParams#columnSpec columnSpecs}. 2760 */ 2761 public static final Alignment FILL = new Alignment() { 2762 @Override 2763 int getGravityOffset(View view, int cellDelta) { 2764 return 0; 2765 } 2766 2767 @Override 2768 public int getAlignmentValue(View view, int viewSize, int mode) { 2769 return UNDEFINED; 2770 } 2771 2772 @Override 2773 public int getSizeInCell(View view, int viewSize, int cellSize) { 2774 return cellSize; 2775 } 2776 }; 2777 2778 static boolean canStretch(int flexibility) { 2779 return (flexibility & CAN_STRETCH) != 0; 2780 } 2781 2782 private static final int INFLEXIBLE = 0; 2783 private static final int CAN_STRETCH = 2; 2784} 2785