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