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