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