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