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