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