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