GridLayout.java revision 6dafd87fb4174447018b044bc67818d54fab57d8
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 17package android.widget; 18 19import android.annotation.IntDef; 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.Insets; 25import android.graphics.Paint; 26import android.util.AttributeSet; 27import android.util.Log; 28import android.util.LogPrinter; 29import android.util.Pair; 30import android.util.Printer; 31import android.view.Gravity; 32import android.view.View; 33import android.view.ViewGroup; 34import android.widget.RemoteViews.RemoteView; 35import com.android.internal.R; 36 37import java.lang.annotation.Retention; 38import java.lang.annotation.RetentionPolicy; 39import java.lang.reflect.Array; 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.HashMap; 43import java.util.List; 44import java.util.Map; 45 46import static android.view.Gravity.*; 47import static android.view.View.MeasureSpec.EXACTLY; 48import static android.view.View.MeasureSpec.makeMeasureSpec; 49import static java.lang.Math.max; 50import 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 161public 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 272 public GridLayout(Context context) { 273 this(context, null); 274 } 275 276 public GridLayout(Context context, AttributeSet attrs) { 277 this(context, attrs, 0); 278 } 279 280 public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) { 281 this(context, attrs, defStyleAttr, 0); 282 } 283 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 605 public void setPrinter(Printer printer) { 606 this.mPrinter = (printer == null) ? NO_PRINTER : printer; 607 } 608 609 // Static utility methods 610 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") 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 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*/ 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 657 private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) { 658 return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading); 659 } 660 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 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 682 private int getMargin(View view, boolean horizontal, boolean leading) { 683 if (mAlignmentMode == ALIGN_MARGINS) { 684 return getMargin1(view, horizontal, leading); 685 } else { 686 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 687 int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins(); 688 LayoutParams lp = getLayoutParams(view); 689 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 690 int index = leading ? spec.span.min : spec.span.max; 691 return margins[index]; 692 } 693 } 694 695 private int getTotalMargin(View child, boolean horizontal) { 696 return getMargin(child, horizontal, true) + getMargin(child, horizontal, false); 697 } 698 699 private static boolean fits(int[] a, int value, int start, int end) { 700 if (end > a.length) { 701 return false; 702 } 703 for (int i = start; i < end; i++) { 704 if (a[i] > value) { 705 return false; 706 } 707 } 708 return true; 709 } 710 711 private static void procrusteanFill(int[] a, int start, int end, int value) { 712 int length = a.length; 713 Arrays.fill(a, Math.min(start, length), Math.min(end, length), value); 714 } 715 716 private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) { 717 lp.setRowSpecSpan(new Interval(row, row + rowSpan)); 718 lp.setColumnSpecSpan(new Interval(col, col + colSpan)); 719 } 720 721 // Logic to avert infinite loops by ensuring that the cells can be placed somewhere. 722 private static int clip(Interval minorRange, boolean minorWasDefined, int count) { 723 int size = minorRange.size(); 724 if (count == 0) { 725 return size; 726 } 727 int min = minorWasDefined ? min(minorRange.min, count) : 0; 728 return min(size, count - min); 729 } 730 731 // install default indices for cells that don't define them 732 private void validateLayoutParams() { 733 final boolean horizontal = (mOrientation == HORIZONTAL); 734 final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 735 final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0; 736 737 int major = 0; 738 int minor = 0; 739 int[] maxSizes = new int[count]; 740 741 for (int i = 0, N = getChildCount(); i < N; i++) { 742 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 743 744 final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec; 745 final Interval majorRange = majorSpec.span; 746 final boolean majorWasDefined = majorSpec.startDefined; 747 final int majorSpan = majorRange.size(); 748 if (majorWasDefined) { 749 major = majorRange.min; 750 } 751 752 final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec; 753 final Interval minorRange = minorSpec.span; 754 final boolean minorWasDefined = minorSpec.startDefined; 755 final int minorSpan = clip(minorRange, minorWasDefined, count); 756 if (minorWasDefined) { 757 minor = minorRange.min; 758 } 759 760 if (count != 0) { 761 // Find suitable row/col values when at least one is undefined. 762 if (!majorWasDefined || !minorWasDefined) { 763 while (!fits(maxSizes, major, minor, minor + minorSpan)) { 764 if (minorWasDefined) { 765 major++; 766 } else { 767 if (minor + minorSpan <= count) { 768 minor++; 769 } else { 770 minor = 0; 771 major++; 772 } 773 } 774 } 775 } 776 procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan); 777 } 778 779 if (horizontal) { 780 setCellGroup(lp, major, majorSpan, minor, minorSpan); 781 } else { 782 setCellGroup(lp, minor, minorSpan, major, majorSpan); 783 } 784 785 minor = minor + minorSpan; 786 } 787 } 788 789 private void invalidateStructure() { 790 mLastLayoutParamsHashCode = UNINITIALIZED_HASH; 791 mHorizontalAxis.invalidateStructure(); 792 mVerticalAxis.invalidateStructure(); 793 // This can end up being done twice. Better twice than not at all. 794 invalidateValues(); 795 } 796 797 private void invalidateValues() { 798 // Need null check because requestLayout() is called in View's initializer, 799 // before we are set up. 800 if (mHorizontalAxis != null && mVerticalAxis != null) { 801 mHorizontalAxis.invalidateValues(); 802 mVerticalAxis.invalidateValues(); 803 } 804 } 805 806 /** @hide */ 807 @Override 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 818 final LayoutParams getLayoutParams(View c) { 819 return (LayoutParams) c.getLayoutParams(); 820 } 821 822 private static void handleInvalidParams(String msg) { 823 throw new IllegalArgumentException(msg + ". "); 824 } 825 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 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 860 protected LayoutParams generateDefaultLayoutParams() { 861 return new LayoutParams(); 862 } 863 864 @Override 865 public LayoutParams generateLayoutParams(AttributeSet attrs) { 866 return new LayoutParams(getContext(), attrs); 867 } 868 869 @Override 870 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 871 return new LayoutParams(p); 872 } 873 874 // Draw grid 875 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 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 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 // Add/remove 939 940 /** 941 * @hide 942 */ 943 @Override 944 protected void onViewAdded(View child) { 945 super.onViewAdded(child); 946 invalidateStructure(); 947 } 948 949 /** 950 * @hide 951 */ 952 @Override 953 protected void onViewRemoved(View child) { 954 super.onViewRemoved(child); 955 invalidateStructure(); 956 } 957 958 /** 959 * We need to call invalidateStructure() when a child's GONE flag changes state. 960 * This implementation is a catch-all, invalidating on any change in the visibility flags. 961 * 962 * @hide 963 */ 964 @Override 965 protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) { 966 super.onChildVisibilityChanged(child, oldVisibility, newVisibility); 967 if (oldVisibility == GONE || newVisibility == GONE) { 968 invalidateStructure(); 969 } 970 } 971 972 private int computeLayoutParamsHashCode() { 973 int result = 1; 974 for (int i = 0, N = getChildCount(); i < N; i++) { 975 View c = getChildAt(i); 976 if (c.getVisibility() == View.GONE) continue; 977 LayoutParams lp = (LayoutParams) c.getLayoutParams(); 978 result = 31 * result + lp.hashCode(); 979 } 980 return result; 981 } 982 983 private void consistencyCheck() { 984 if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) { 985 validateLayoutParams(); 986 mLastLayoutParamsHashCode = computeLayoutParamsHashCode(); 987 } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) { 988 mPrinter.println("The fields of some layout parameters were modified in between " 989 + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec."); 990 invalidateStructure(); 991 consistencyCheck(); 992 } 993 } 994 995 // Measurement 996 997 // Note: padding has already been removed from the supplied specs 998 private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, 999 int childWidth, int childHeight) { 1000 int childWidthSpec = getChildMeasureSpec(parentWidthSpec, 1001 getTotalMargin(child, true), childWidth); 1002 int childHeightSpec = getChildMeasureSpec(parentHeightSpec, 1003 getTotalMargin(child, false), childHeight); 1004 child.measure(childWidthSpec, childHeightSpec); 1005 } 1006 1007 // Note: padding has already been removed from the supplied specs 1008 private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) { 1009 for (int i = 0, N = getChildCount(); i < N; i++) { 1010 View c = getChildAt(i); 1011 if (c.getVisibility() == View.GONE) continue; 1012 LayoutParams lp = getLayoutParams(c); 1013 if (firstPass) { 1014 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); 1015 mHorizontalAxis.recordOriginalMeasurement(i); 1016 mVerticalAxis.recordOriginalMeasurement(i); 1017 } else { 1018 boolean horizontal = (mOrientation == HORIZONTAL); 1019 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1020 if (spec.getAbsoluteAlignment(horizontal) == FILL) { 1021 Interval span = spec.span; 1022 Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; 1023 int[] locations = axis.getLocations(); 1024 int cellSize = locations[span.max] - locations[span.min]; 1025 int viewSize = cellSize - getTotalMargin(c, horizontal); 1026 if (horizontal) { 1027 measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height); 1028 } else { 1029 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize); 1030 } 1031 } 1032 } 1033 } 1034 } 1035 1036 static int adjust(int measureSpec, int delta) { 1037 return makeMeasureSpec( 1038 MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec)); 1039 } 1040 1041 @Override 1042 protected void onMeasure(int widthSpec, int heightSpec) { 1043 consistencyCheck(); 1044 1045 /** If we have been called by {@link View#measure(int, int)}, one of width or height 1046 * is likely to have changed. We must invalidate if so. */ 1047 invalidateValues(); 1048 1049 int hPadding = getPaddingLeft() + getPaddingRight(); 1050 int vPadding = getPaddingTop() + getPaddingBottom(); 1051 1052 int widthSpecSansPadding = adjust( widthSpec, -hPadding); 1053 int heightSpecSansPadding = adjust(heightSpec, -vPadding); 1054 1055 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true); 1056 1057 int widthSansPadding; 1058 int heightSansPadding; 1059 1060 // Use the orientation property to decide which axis should be laid out first. 1061 if (mOrientation == HORIZONTAL) { 1062 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding); 1063 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false); 1064 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding); 1065 } else { 1066 heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding); 1067 measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false); 1068 widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding); 1069 } 1070 1071 int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth()); 1072 int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight()); 1073 1074 setMeasuredDimension( 1075 resolveSizeAndState(measuredWidth, widthSpec, 0), 1076 resolveSizeAndState(measuredHeight, heightSpec, 0)); 1077 } 1078 1079 private int getMeasurement(View c, boolean horizontal) { 1080 return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); 1081 } 1082 1083 final int getMeasurementIncludingMargin(View c, boolean horizontal) { 1084 if (c.getVisibility() == View.GONE) { 1085 return 0; 1086 } 1087 return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal); 1088 } 1089 1090 @Override 1091 public void requestLayout() { 1092 super.requestLayout(); 1093 invalidateValues(); 1094 } 1095 1096 // Layout container 1097 1098 /** 1099 * {@inheritDoc} 1100 */ 1101 /* 1102 The layout operation is implemented by delegating the heavy lifting to the 1103 to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class. 1104 Together they compute the locations of the vertical and horizontal lines of 1105 the grid (respectively!). 1106 1107 This method is then left with the simpler task of applying margins, gravity 1108 and sizing to each child view and then placing it in its cell. 1109 */ 1110 @Override 1111 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1112 consistencyCheck(); 1113 1114 int targetWidth = right - left; 1115 int targetHeight = bottom - top; 1116 1117 int paddingLeft = getPaddingLeft(); 1118 int paddingTop = getPaddingTop(); 1119 int paddingRight = getPaddingRight(); 1120 int paddingBottom = getPaddingBottom(); 1121 1122 mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight); 1123 mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom); 1124 1125 int[] hLocations = mHorizontalAxis.getLocations(); 1126 int[] vLocations = mVerticalAxis.getLocations(); 1127 1128 for (int i = 0, N = getChildCount(); i < N; i++) { 1129 View c = getChildAt(i); 1130 if (c.getVisibility() == View.GONE) continue; 1131 LayoutParams lp = getLayoutParams(c); 1132 Spec columnSpec = lp.columnSpec; 1133 Spec rowSpec = lp.rowSpec; 1134 1135 Interval colSpan = columnSpec.span; 1136 Interval rowSpan = rowSpec.span; 1137 1138 int x1 = hLocations[colSpan.min]; 1139 int y1 = vLocations[rowSpan.min]; 1140 1141 int x2 = hLocations[colSpan.max]; 1142 int y2 = vLocations[rowSpan.max]; 1143 1144 int cellWidth = x2 - x1; 1145 int cellHeight = y2 - y1; 1146 1147 int pWidth = getMeasurement(c, true); 1148 int pHeight = getMeasurement(c, false); 1149 1150 Alignment hAlign = columnSpec.getAbsoluteAlignment(true); 1151 Alignment vAlign = rowSpec.getAbsoluteAlignment(false); 1152 1153 Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i); 1154 Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i); 1155 1156 // Gravity offsets: the location of the alignment group relative to its cell group. 1157 int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true)); 1158 int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true)); 1159 1160 int leftMargin = getMargin(c, true, true); 1161 int topMargin = getMargin(c, false, true); 1162 int rightMargin = getMargin(c, true, false); 1163 int bottomMargin = getMargin(c, false, false); 1164 1165 int sumMarginsX = leftMargin + rightMargin; 1166 int sumMarginsY = topMargin + bottomMargin; 1167 1168 // Alignment offsets: the location of the view relative to its alignment group. 1169 int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true); 1170 int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false); 1171 1172 int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX); 1173 int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY); 1174 1175 int dx = x1 + gravityOffsetX + alignmentOffsetX; 1176 1177 int cx = !isLayoutRtl() ? paddingLeft + leftMargin + dx : 1178 targetWidth - width - paddingRight - rightMargin - dx; 1179 int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin; 1180 1181 if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) { 1182 c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); 1183 } 1184 c.layout(cx, cy, cx + width, cy + height); 1185 } 1186 } 1187 1188 @Override 1189 public CharSequence getAccessibilityClassName() { 1190 return GridLayout.class.getName(); 1191 } 1192 1193 // Inner classes 1194 1195 /* 1196 This internal class houses the algorithm for computing the locations of grid lines; 1197 along either the horizontal or vertical axis. A GridLayout uses two instances of this class - 1198 distinguished by the "horizontal" flag which is true for the horizontal axis and false 1199 for the vertical one. 1200 */ 1201 final class Axis { 1202 private static final int NEW = 0; 1203 private static final int PENDING = 1; 1204 private static final int COMPLETE = 2; 1205 1206 public final boolean horizontal; 1207 1208 public int definedCount = UNDEFINED; 1209 private int maxIndex = UNDEFINED; 1210 1211 PackedMap<Spec, Bounds> groupBounds; 1212 public boolean groupBoundsValid = false; 1213 1214 PackedMap<Interval, MutableInt> forwardLinks; 1215 public boolean forwardLinksValid = false; 1216 1217 PackedMap<Interval, MutableInt> backwardLinks; 1218 public boolean backwardLinksValid = false; 1219 1220 public int[] leadingMargins; 1221 public boolean leadingMarginsValid = false; 1222 1223 public int[] trailingMargins; 1224 public boolean trailingMarginsValid = false; 1225 1226 public Arc[] arcs; 1227 public boolean arcsValid = false; 1228 1229 public int[] locations; 1230 public boolean locationsValid = false; 1231 1232 public boolean hasWeights; 1233 public boolean hasWeightsValid = false; 1234 public int[] originalMeasurements; 1235 public int[] deltas; 1236 1237 boolean orderPreserved = DEFAULT_ORDER_PRESERVED; 1238 1239 private MutableInt parentMin = new MutableInt(0); 1240 private MutableInt parentMax = new MutableInt(-MAX_SIZE); 1241 1242 private Axis(boolean horizontal) { 1243 this.horizontal = horizontal; 1244 } 1245 1246 private int calculateMaxIndex() { 1247 // the number Integer.MIN_VALUE + 1 comes up in undefined cells 1248 int result = -1; 1249 for (int i = 0, N = getChildCount(); i < N; i++) { 1250 View c = getChildAt(i); 1251 LayoutParams params = getLayoutParams(c); 1252 Spec spec = horizontal ? params.columnSpec : params.rowSpec; 1253 Interval span = spec.span; 1254 result = max(result, span.min); 1255 result = max(result, span.max); 1256 result = max(result, span.size()); 1257 } 1258 return result == -1 ? UNDEFINED : result; 1259 } 1260 1261 private int getMaxIndex() { 1262 if (maxIndex == UNDEFINED) { 1263 maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children 1264 } 1265 return maxIndex; 1266 } 1267 1268 public int getCount() { 1269 return max(definedCount, getMaxIndex()); 1270 } 1271 1272 public void setCount(int count) { 1273 if (count != UNDEFINED && count < getMaxIndex()) { 1274 handleInvalidParams((horizontal ? "column" : "row") + 1275 "Count must be greater than or equal to the maximum of all grid indices " + 1276 "(and spans) defined in the LayoutParams of each child"); 1277 } 1278 this.definedCount = count; 1279 } 1280 1281 public boolean isOrderPreserved() { 1282 return orderPreserved; 1283 } 1284 1285 public void setOrderPreserved(boolean orderPreserved) { 1286 this.orderPreserved = orderPreserved; 1287 invalidateStructure(); 1288 } 1289 1290 private PackedMap<Spec, Bounds> createGroupBounds() { 1291 Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class); 1292 for (int i = 0, N = getChildCount(); i < N; i++) { 1293 View c = getChildAt(i); 1294 // we must include views that are GONE here, see introductory javadoc 1295 LayoutParams lp = getLayoutParams(c); 1296 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1297 Bounds bounds = spec.getAbsoluteAlignment(horizontal).getBounds(); 1298 assoc.put(spec, bounds); 1299 } 1300 return assoc.pack(); 1301 } 1302 1303 private void computeGroupBounds() { 1304 Bounds[] values = groupBounds.values; 1305 for (int i = 0; i < values.length; i++) { 1306 values[i].reset(); 1307 } 1308 for (int i = 0, N = getChildCount(); i < N; i++) { 1309 View c = getChildAt(i); 1310 // we must include views that are GONE here, see introductory javadoc 1311 LayoutParams lp = getLayoutParams(c); 1312 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1313 int size = (spec.weight == 0) ? 1314 getMeasurementIncludingMargin(c, horizontal) : 1315 getOriginalMeasurements()[i] + getDeltas()[i]; 1316 groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size); 1317 } 1318 } 1319 1320 public PackedMap<Spec, Bounds> getGroupBounds() { 1321 if (groupBounds == null) { 1322 groupBounds = createGroupBounds(); 1323 } 1324 if (!groupBoundsValid) { 1325 computeGroupBounds(); 1326 groupBoundsValid = true; 1327 } 1328 return groupBounds; 1329 } 1330 1331 // Add values computed by alignment - taking the max of all alignments in each span 1332 private PackedMap<Interval, MutableInt> createLinks(boolean min) { 1333 Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class); 1334 Spec[] keys = getGroupBounds().keys; 1335 for (int i = 0, N = keys.length; i < N; i++) { 1336 Interval span = min ? keys[i].span : keys[i].span.inverse(); 1337 result.put(span, new MutableInt()); 1338 } 1339 return result.pack(); 1340 } 1341 1342 private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) { 1343 MutableInt[] spans = links.values; 1344 for (int i = 0; i < spans.length; i++) { 1345 spans[i].reset(); 1346 } 1347 1348 // Use getter to trigger a re-evaluation 1349 Bounds[] bounds = getGroupBounds().values; 1350 for (int i = 0; i < bounds.length; i++) { 1351 int size = bounds[i].size(min); 1352 MutableInt valueHolder = links.getValue(i); 1353 // this effectively takes the max() of the minima and the min() of the maxima 1354 valueHolder.value = max(valueHolder.value, min ? size : -size); 1355 } 1356 } 1357 1358 private PackedMap<Interval, MutableInt> getForwardLinks() { 1359 if (forwardLinks == null) { 1360 forwardLinks = createLinks(true); 1361 } 1362 if (!forwardLinksValid) { 1363 computeLinks(forwardLinks, true); 1364 forwardLinksValid = true; 1365 } 1366 return forwardLinks; 1367 } 1368 1369 private PackedMap<Interval, MutableInt> getBackwardLinks() { 1370 if (backwardLinks == null) { 1371 backwardLinks = createLinks(false); 1372 } 1373 if (!backwardLinksValid) { 1374 computeLinks(backwardLinks, false); 1375 backwardLinksValid = true; 1376 } 1377 return backwardLinks; 1378 } 1379 1380 private void include(List<Arc> arcs, Interval key, MutableInt size, 1381 boolean ignoreIfAlreadyPresent) { 1382 /* 1383 Remove self referential links. 1384 These appear: 1385 . as parental constraints when GridLayout has no children 1386 . when components have been marked as GONE 1387 */ 1388 if (key.size() == 0) { 1389 return; 1390 } 1391 // this bit below should really be computed outside here - 1392 // its just to stop default (row/col > 0) constraints obliterating valid entries 1393 if (ignoreIfAlreadyPresent) { 1394 for (Arc arc : arcs) { 1395 Interval span = arc.span; 1396 if (span.equals(key)) { 1397 return; 1398 } 1399 } 1400 } 1401 arcs.add(new Arc(key, size)); 1402 } 1403 1404 private void include(List<Arc> arcs, Interval key, MutableInt size) { 1405 include(arcs, key, size, true); 1406 } 1407 1408 // Group arcs by their first vertex, returning an array of arrays. 1409 // This is linear in the number of arcs. 1410 Arc[][] groupArcsByFirstVertex(Arc[] arcs) { 1411 int N = getCount() + 1; // the number of vertices 1412 Arc[][] result = new Arc[N][]; 1413 int[] sizes = new int[N]; 1414 for (Arc arc : arcs) { 1415 sizes[arc.span.min]++; 1416 } 1417 for (int i = 0; i < sizes.length; i++) { 1418 result[i] = new Arc[sizes[i]]; 1419 } 1420 // reuse the sizes array to hold the current last elements as we insert each arc 1421 Arrays.fill(sizes, 0); 1422 for (Arc arc : arcs) { 1423 int i = arc.span.min; 1424 result[i][sizes[i]++] = arc; 1425 } 1426 1427 return result; 1428 } 1429 1430 private Arc[] topologicalSort(final Arc[] arcs) { 1431 return new Object() { 1432 Arc[] result = new Arc[arcs.length]; 1433 int cursor = result.length - 1; 1434 Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); 1435 int[] visited = new int[getCount() + 1]; 1436 1437 void walk(int loc) { 1438 switch (visited[loc]) { 1439 case NEW: { 1440 visited[loc] = PENDING; 1441 for (Arc arc : arcsByVertex[loc]) { 1442 walk(arc.span.max); 1443 result[cursor--] = arc; 1444 } 1445 visited[loc] = COMPLETE; 1446 break; 1447 } 1448 case PENDING: { 1449 // le singe est dans l'arbre 1450 assert false; 1451 break; 1452 } 1453 case COMPLETE: { 1454 break; 1455 } 1456 } 1457 } 1458 1459 Arc[] sort() { 1460 for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { 1461 walk(loc); 1462 } 1463 assert cursor == -1; 1464 return result; 1465 } 1466 }.sort(); 1467 } 1468 1469 private Arc[] topologicalSort(List<Arc> arcs) { 1470 return topologicalSort(arcs.toArray(new Arc[arcs.size()])); 1471 } 1472 1473 private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) { 1474 for (int i = 0; i < links.keys.length; i++) { 1475 Interval key = links.keys[i]; 1476 include(result, key, links.values[i], false); 1477 } 1478 } 1479 1480 private Arc[] createArcs() { 1481 List<Arc> mins = new ArrayList<Arc>(); 1482 List<Arc> maxs = new ArrayList<Arc>(); 1483 1484 // Add the minimum values from the components. 1485 addComponentSizes(mins, getForwardLinks()); 1486 // Add the maximum values from the components. 1487 addComponentSizes(maxs, getBackwardLinks()); 1488 1489 // Add ordering constraints to prevent row/col sizes from going negative 1490 if (orderPreserved) { 1491 // Add a constraint for every row/col 1492 for (int i = 0; i < getCount(); i++) { 1493 include(mins, new Interval(i, i + 1), new MutableInt(0)); 1494 } 1495 } 1496 1497 // Add the container constraints. Use the version of include that allows 1498 // duplicate entries in case a child spans the entire grid. 1499 int N = getCount(); 1500 include(mins, new Interval(0, N), parentMin, false); 1501 include(maxs, new Interval(N, 0), parentMax, false); 1502 1503 // Sort 1504 Arc[] sMins = topologicalSort(mins); 1505 Arc[] sMaxs = topologicalSort(maxs); 1506 1507 return append(sMins, sMaxs); 1508 } 1509 1510 private void computeArcs() { 1511 // getting the links validates the values that are shared by the arc list 1512 getForwardLinks(); 1513 getBackwardLinks(); 1514 } 1515 1516 public Arc[] getArcs() { 1517 if (arcs == null) { 1518 arcs = createArcs(); 1519 } 1520 if (!arcsValid) { 1521 computeArcs(); 1522 arcsValid = true; 1523 } 1524 return arcs; 1525 } 1526 1527 private boolean relax(int[] locations, Arc entry) { 1528 if (!entry.valid) { 1529 return false; 1530 } 1531 Interval span = entry.span; 1532 int u = span.min; 1533 int v = span.max; 1534 int value = entry.value.value; 1535 int candidate = locations[u] + value; 1536 if (candidate > locations[v]) { 1537 locations[v] = candidate; 1538 return true; 1539 } 1540 return false; 1541 } 1542 1543 private void init(int[] locations) { 1544 Arrays.fill(locations, 0); 1545 } 1546 1547 private String arcsToString(List<Arc> arcs) { 1548 String var = horizontal ? "x" : "y"; 1549 StringBuilder result = new StringBuilder(); 1550 boolean first = true; 1551 for (Arc arc : arcs) { 1552 if (first) { 1553 first = false; 1554 } else { 1555 result = result.append(", "); 1556 } 1557 int src = arc.span.min; 1558 int dst = arc.span.max; 1559 int value = arc.value.value; 1560 result.append((src < dst) ? 1561 var + dst + "-" + var + src + ">=" + value : 1562 var + src + "-" + var + dst + "<=" + -value); 1563 1564 } 1565 return result.toString(); 1566 } 1567 1568 private void logError(String axisName, Arc[] arcs, boolean[] culprits0) { 1569 List<Arc> culprits = new ArrayList<Arc>(); 1570 List<Arc> removed = new ArrayList<Arc>(); 1571 for (int c = 0; c < arcs.length; c++) { 1572 Arc arc = arcs[c]; 1573 if (culprits0[c]) { 1574 culprits.add(arc); 1575 } 1576 if (!arc.valid) { 1577 removed.add(arc); 1578 } 1579 } 1580 mPrinter.println(axisName + " constraints: " + arcsToString(culprits) + 1581 " are inconsistent; permanently removing: " + arcsToString(removed) + ". "); 1582 } 1583 1584 /* 1585 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N) 1586 1587 GridLayout converts its requirements into a system of linear constraints of the 1588 form: 1589 1590 x[i] - x[j] < a[k] 1591 1592 Where the x[i] are variables and the a[k] are constants. 1593 1594 For example, if the variables were instead labeled x, y, z we might have: 1595 1596 x - y < 17 1597 y - z < 23 1598 z - x < 42 1599 1600 This is a special case of the Linear Programming problem that is, in turn, 1601 equivalent to the single-source shortest paths problem on a digraph, for 1602 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution. 1603 */ 1604 private boolean solve(Arc[] arcs, int[] locations) { 1605 return solve(arcs, locations, true); 1606 } 1607 1608 private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) { 1609 String axisName = horizontal ? "horizontal" : "vertical"; 1610 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. 1611 boolean[] originalCulprits = null; 1612 1613 for (int p = 0; p < arcs.length; p++) { 1614 init(locations); 1615 1616 // We take one extra pass over traditional Bellman-Ford (and omit their final step) 1617 for (int i = 0; i < N; i++) { 1618 boolean changed = false; 1619 for (int j = 0, length = arcs.length; j < length; j++) { 1620 changed |= relax(locations, arcs[j]); 1621 } 1622 if (!changed) { 1623 if (originalCulprits != null) { 1624 logError(axisName, arcs, originalCulprits); 1625 } 1626 return true; 1627 } 1628 } 1629 1630 if (!modifyOnError) { 1631 return false; // cannot solve with these constraints 1632 } 1633 1634 boolean[] culprits = new boolean[arcs.length]; 1635 for (int i = 0; i < N; i++) { 1636 for (int j = 0, length = arcs.length; j < length; j++) { 1637 culprits[j] |= relax(locations, arcs[j]); 1638 } 1639 } 1640 1641 if (p == 0) { 1642 originalCulprits = culprits; 1643 } 1644 1645 for (int i = 0; i < arcs.length; i++) { 1646 if (culprits[i]) { 1647 Arc arc = arcs[i]; 1648 // Only remove max values, min values alone cannot be inconsistent 1649 if (arc.span.min < arc.span.max) { 1650 continue; 1651 } 1652 arc.valid = false; 1653 break; 1654 } 1655 } 1656 } 1657 return true; 1658 } 1659 1660 private void computeMargins(boolean leading) { 1661 int[] margins = leading ? leadingMargins : trailingMargins; 1662 for (int i = 0, N = getChildCount(); i < N; i++) { 1663 View c = getChildAt(i); 1664 if (c.getVisibility() == View.GONE) continue; 1665 LayoutParams lp = getLayoutParams(c); 1666 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1667 Interval span = spec.span; 1668 int index = leading ? span.min : span.max; 1669 margins[index] = max(margins[index], getMargin1(c, horizontal, leading)); 1670 } 1671 } 1672 1673 // External entry points 1674 1675 public int[] getLeadingMargins() { 1676 if (leadingMargins == null) { 1677 leadingMargins = new int[getCount() + 1]; 1678 } 1679 if (!leadingMarginsValid) { 1680 computeMargins(true); 1681 leadingMarginsValid = true; 1682 } 1683 return leadingMargins; 1684 } 1685 1686 public int[] getTrailingMargins() { 1687 if (trailingMargins == null) { 1688 trailingMargins = new int[getCount() + 1]; 1689 } 1690 if (!trailingMarginsValid) { 1691 computeMargins(false); 1692 trailingMarginsValid = true; 1693 } 1694 return trailingMargins; 1695 } 1696 1697 private boolean solve(int[] a) { 1698 return solve(getArcs(), a); 1699 } 1700 1701 private boolean computeHasWeights() { 1702 for (int i = 0, N = getChildCount(); i < N; i++) { 1703 final View child = getChildAt(i); 1704 if (child.getVisibility() == View.GONE) { 1705 continue; 1706 } 1707 LayoutParams lp = getLayoutParams(child); 1708 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1709 if (spec.weight != 0) { 1710 return true; 1711 } 1712 } 1713 return false; 1714 } 1715 1716 private boolean hasWeights() { 1717 if (!hasWeightsValid) { 1718 hasWeights = computeHasWeights(); 1719 hasWeightsValid = true; 1720 } 1721 return hasWeights; 1722 } 1723 1724 public int[] getOriginalMeasurements() { 1725 if (originalMeasurements == null) { 1726 originalMeasurements = new int[getChildCount()]; 1727 } 1728 return originalMeasurements; 1729 } 1730 1731 private void recordOriginalMeasurement(int i) { 1732 if (hasWeights()) { 1733 getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal); 1734 } 1735 } 1736 1737 public int[] getDeltas() { 1738 if (deltas == null) { 1739 deltas = new int[getChildCount()]; 1740 } 1741 return deltas; 1742 } 1743 1744 private void shareOutDelta(int totalDelta, float totalWeight) { 1745 Arrays.fill(deltas, 0); 1746 for (int i = 0, N = getChildCount(); i < N; i++) { 1747 final View c = getChildAt(i); 1748 if (c.getVisibility() == View.GONE) { 1749 continue; 1750 } 1751 LayoutParams lp = getLayoutParams(c); 1752 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1753 float weight = spec.weight; 1754 if (weight != 0) { 1755 int delta = Math.round((weight * totalDelta / totalWeight)); 1756 deltas[i] = delta; 1757 // the two adjustments below are to counter the above rounding and avoid 1758 // off-by-ones at the end 1759 totalDelta -= delta; 1760 totalWeight -= weight; 1761 } 1762 } 1763 } 1764 1765 private void solveAndDistributeSpace(int[] a) { 1766 Arrays.fill(getDeltas(), 0); 1767 solve(a); 1768 int deltaMax = parentMin.value * getChildCount() + 1; //exclusive 1769 if (deltaMax < 2) { 1770 return; //don't have any delta to distribute 1771 } 1772 int deltaMin = 0; //inclusive 1773 1774 float totalWeight = calculateTotalWeight(); 1775 1776 int validDelta = -1; //delta for which a solution exists 1777 boolean validSolution = true; 1778 // do a binary search to find the max delta that won't conflict with constraints 1779 while(deltaMin < deltaMax) { 1780 final int delta = (deltaMin + deltaMax) / 2; 1781 invalidateValues(); 1782 shareOutDelta(delta, totalWeight); 1783 validSolution = solve(getArcs(), a, false); 1784 if (validSolution) { 1785 validDelta = delta; 1786 deltaMin = delta + 1; 1787 } else { 1788 deltaMax = delta; 1789 } 1790 } 1791 if (validDelta > 0 && !validSolution) { 1792 // last solution was not successful but we have a successful one. Use it. 1793 invalidateValues(); 1794 shareOutDelta(validDelta, totalWeight); 1795 solve(a); 1796 } 1797 } 1798 1799 private float calculateTotalWeight() { 1800 float totalWeight = 0f; 1801 for (int i = 0, N = getChildCount(); i < N; i++) { 1802 View c = getChildAt(i); 1803 if (c.getVisibility() == View.GONE) { 1804 continue; 1805 } 1806 LayoutParams lp = getLayoutParams(c); 1807 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1808 totalWeight += spec.weight; 1809 } 1810 return totalWeight; 1811 } 1812 1813 private void computeLocations(int[] a) { 1814 if (!hasWeights()) { 1815 solve(a); 1816 } else { 1817 solveAndDistributeSpace(a); 1818 } 1819 if (!orderPreserved) { 1820 // Solve returns the smallest solution to the constraint system for which all 1821 // values are positive. One value is therefore zero - though if the row/col 1822 // order is not preserved this may not be the first vertex. For consistency, 1823 // translate all the values so that they measure the distance from a[0]; the 1824 // leading edge of the parent. After this transformation some values may be 1825 // negative. 1826 int a0 = a[0]; 1827 for (int i = 0, N = a.length; i < N; i++) { 1828 a[i] = a[i] - a0; 1829 } 1830 } 1831 } 1832 1833 public int[] getLocations() { 1834 if (locations == null) { 1835 int N = getCount() + 1; 1836 locations = new int[N]; 1837 } 1838 if (!locationsValid) { 1839 computeLocations(locations); 1840 locationsValid = true; 1841 } 1842 return locations; 1843 } 1844 1845 private int size(int[] locations) { 1846 // The parental edges are attached to vertices 0 and N - even when order is not 1847 // being preserved and other vertices fall outside this range. Measure the distance 1848 // between vertices 0 and N, assuming that locations[0] = 0. 1849 return locations[getCount()]; 1850 } 1851 1852 private void setParentConstraints(int min, int max) { 1853 parentMin.value = min; 1854 parentMax.value = -max; 1855 locationsValid = false; 1856 } 1857 1858 private int getMeasure(int min, int max) { 1859 setParentConstraints(min, max); 1860 return size(getLocations()); 1861 } 1862 1863 public int getMeasure(int measureSpec) { 1864 int mode = MeasureSpec.getMode(measureSpec); 1865 int size = MeasureSpec.getSize(measureSpec); 1866 switch (mode) { 1867 case MeasureSpec.UNSPECIFIED: { 1868 return getMeasure(0, MAX_SIZE); 1869 } 1870 case MeasureSpec.EXACTLY: { 1871 return getMeasure(size, size); 1872 } 1873 case MeasureSpec.AT_MOST: { 1874 return getMeasure(0, size); 1875 } 1876 default: { 1877 assert false; 1878 return 0; 1879 } 1880 } 1881 } 1882 1883 public void layout(int size) { 1884 setParentConstraints(size, size); 1885 getLocations(); 1886 } 1887 1888 public void invalidateStructure() { 1889 maxIndex = UNDEFINED; 1890 1891 groupBounds = null; 1892 forwardLinks = null; 1893 backwardLinks = null; 1894 1895 leadingMargins = null; 1896 trailingMargins = null; 1897 arcs = null; 1898 1899 locations = null; 1900 1901 originalMeasurements = null; 1902 deltas = null; 1903 hasWeightsValid = false; 1904 1905 invalidateValues(); 1906 } 1907 1908 public void invalidateValues() { 1909 groupBoundsValid = false; 1910 forwardLinksValid = false; 1911 backwardLinksValid = false; 1912 1913 leadingMarginsValid = false; 1914 trailingMarginsValid = false; 1915 arcsValid = false; 1916 1917 locationsValid = false; 1918 } 1919 } 1920 1921 /** 1922 * Layout information associated with each of the children of a GridLayout. 1923 * <p> 1924 * GridLayout supports both row and column spanning and arbitrary forms of alignment within 1925 * each cell group. The fundamental parameters associated with each cell group are 1926 * gathered into their vertical and horizontal components and stored 1927 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters. 1928 * {@link GridLayout.Spec Specs} are immutable structures 1929 * and may be shared between the layout parameters of different children. 1930 * <p> 1931 * The row and column specs contain the leading and trailing indices along each axis 1932 * and together specify the four grid indices that delimit the cells of this cell group. 1933 * <p> 1934 * The alignment properties of the row and column specs together specify 1935 * both aspects of alignment within the cell group. It is also possible to specify a child's 1936 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} 1937 * method. 1938 * <p> 1939 * The weight property is also included in Spec and specifies the proportion of any 1940 * excess space that is due to the associated view. 1941 * 1942 * <h4>WRAP_CONTENT and MATCH_PARENT</h4> 1943 * 1944 * Because the default values of the {@link #width} and {@link #height} 1945 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly 1946 * declared in the layout parameters of GridLayout's children. In addition, 1947 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from 1948 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is 1949 * instead controlled by the principle of <em>flexibility</em>, 1950 * as discussed in {@link GridLayout}. 1951 * 1952 * <h4>Summary</h4> 1953 * 1954 * You should not need to use either of the special size values: 1955 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of 1956 * a GridLayout. 1957 * 1958 * <h4>Default values</h4> 1959 * 1960 * <ul> 1961 * <li>{@link #width} = {@link #WRAP_CONTENT}</li> 1962 * <li>{@link #height} = {@link #WRAP_CONTENT}</li> 1963 * <li>{@link #topMargin} = 0 when 1964 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1965 * {@code false}; otherwise {@link #UNDEFINED}, to 1966 * indicate that a default value should be computed on demand. </li> 1967 * <li>{@link #leftMargin} = 0 when 1968 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1969 * {@code false}; otherwise {@link #UNDEFINED}, to 1970 * indicate that a default value should be computed on demand. </li> 1971 * <li>{@link #bottomMargin} = 0 when 1972 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1973 * {@code false}; otherwise {@link #UNDEFINED}, to 1974 * indicate that a default value should be computed on demand. </li> 1975 * <li>{@link #rightMargin} = 0 when 1976 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1977 * {@code false}; otherwise {@link #UNDEFINED}, to 1978 * indicate that a default value should be computed on demand. </li> 1979 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> 1980 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> 1981 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> 1982 * <li>{@link #rowSpec}<code>.weight</code> = 0 </li> 1983 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> 1984 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> 1985 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> 1986 * <li>{@link #columnSpec}<code>.weight</code> = 0 </li> 1987 * </ul> 1988 * 1989 * See {@link GridLayout} for a more complete description of the conventions 1990 * used by GridLayout in the interpretation of the properties of this class. 1991 * 1992 * @attr ref android.R.styleable#GridLayout_Layout_layout_row 1993 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan 1994 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight 1995 * @attr ref android.R.styleable#GridLayout_Layout_layout_column 1996 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan 1997 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight 1998 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1999 */ 2000 public static class LayoutParams extends MarginLayoutParams { 2001 2002 // Default values 2003 2004 private static final int DEFAULT_WIDTH = WRAP_CONTENT; 2005 private static final int DEFAULT_HEIGHT = WRAP_CONTENT; 2006 private static final int DEFAULT_MARGIN = UNDEFINED; 2007 private static final int DEFAULT_ROW = UNDEFINED; 2008 private static final int DEFAULT_COLUMN = UNDEFINED; 2009 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); 2010 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); 2011 2012 // TypedArray indices 2013 2014 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin; 2015 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft; 2016 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop; 2017 private static final int RIGHT_MARGIN = 2018 R.styleable.ViewGroup_MarginLayout_layout_marginRight; 2019 private static final int BOTTOM_MARGIN = 2020 R.styleable.ViewGroup_MarginLayout_layout_marginBottom; 2021 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; 2022 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; 2023 private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight; 2024 2025 private static final int ROW = R.styleable.GridLayout_Layout_layout_row; 2026 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; 2027 private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight; 2028 2029 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; 2030 2031 // Instance variables 2032 2033 /** 2034 * The spec that defines the vertical characteristics of the cell group 2035 * described by these layout parameters. 2036 * If an assignment is made to this field after a measurement or layout operation 2037 * has already taken place, a call to 2038 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 2039 * must be made to notify GridLayout of the change. GridLayout is normally able 2040 * to detect when code fails to observe this rule, issue a warning and take steps to 2041 * compensate for the omission. This facility is implemented on a best effort basis 2042 * and should not be relied upon in production code - so it is best to include the above 2043 * calls to remove the warnings as soon as it is practical. 2044 */ 2045 public Spec rowSpec = Spec.UNDEFINED; 2046 2047 /** 2048 * The spec that defines the horizontal characteristics of the cell group 2049 * described by these layout parameters. 2050 * If an assignment is made to this field after a measurement or layout operation 2051 * has already taken place, a call to 2052 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 2053 * must be made to notify GridLayout of the change. GridLayout is normally able 2054 * to detect when code fails to observe this rule, issue a warning and take steps to 2055 * compensate for the omission. This facility is implemented on a best effort basis 2056 * and should not be relied upon in production code - so it is best to include the above 2057 * calls to remove the warnings as soon as it is practical. 2058 */ 2059 public Spec columnSpec = Spec.UNDEFINED; 2060 2061 // Constructors 2062 2063 private LayoutParams( 2064 int width, int height, 2065 int left, int top, int right, int bottom, 2066 Spec rowSpec, Spec columnSpec) { 2067 super(width, height); 2068 setMargins(left, top, right, bottom); 2069 this.rowSpec = rowSpec; 2070 this.columnSpec = columnSpec; 2071 } 2072 2073 /** 2074 * Constructs a new LayoutParams instance for this <code>rowSpec</code> 2075 * and <code>columnSpec</code>. All other fields are initialized with 2076 * default values as defined in {@link LayoutParams}. 2077 * 2078 * @param rowSpec the rowSpec 2079 * @param columnSpec the columnSpec 2080 */ 2081 public LayoutParams(Spec rowSpec, Spec columnSpec) { 2082 this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 2083 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 2084 rowSpec, columnSpec); 2085 } 2086 2087 /** 2088 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 2089 */ 2090 public LayoutParams() { 2091 this(Spec.UNDEFINED, Spec.UNDEFINED); 2092 } 2093 2094 // Copying constructors 2095 2096 /** 2097 * {@inheritDoc} 2098 */ 2099 public LayoutParams(ViewGroup.LayoutParams params) { 2100 super(params); 2101 } 2102 2103 /** 2104 * {@inheritDoc} 2105 */ 2106 public LayoutParams(MarginLayoutParams params) { 2107 super(params); 2108 } 2109 2110 /** 2111 * Copy constructor. Clones the width, height, margin values, row spec, 2112 * and column spec of the source. 2113 * 2114 * @param source The layout params to copy from. 2115 */ 2116 public LayoutParams(LayoutParams source) { 2117 super(source); 2118 2119 this.rowSpec = source.rowSpec; 2120 this.columnSpec = source.columnSpec; 2121 } 2122 2123 // AttributeSet constructors 2124 2125 /** 2126 * {@inheritDoc} 2127 * 2128 * Values not defined in the attribute set take the default values 2129 * defined in {@link LayoutParams}. 2130 */ 2131 public LayoutParams(Context context, AttributeSet attrs) { 2132 super(context, attrs); 2133 reInitSuper(context, attrs); 2134 init(context, attrs); 2135 } 2136 2137 // Implementation 2138 2139 // Reinitialise the margins using a different default policy than MarginLayoutParams. 2140 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 2141 // so that a layout manager default can be accessed post set up. We need this as, at the 2142 // point of installation, we do not know how many rows/cols there are and therefore 2143 // which elements are positioned next to the container's trailing edges. We need to 2144 // know this as margins around the container's boundary should have different 2145 // defaults to those between peers. 2146 2147 // This method could be parametrized and moved into MarginLayout. 2148 private void reInitSuper(Context context, AttributeSet attrs) { 2149 TypedArray a = 2150 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 2151 try { 2152 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 2153 2154 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 2155 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 2156 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 2157 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 2158 } finally { 2159 a.recycle(); 2160 } 2161 } 2162 2163 private void init(Context context, AttributeSet attrs) { 2164 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); 2165 try { 2166 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); 2167 2168 int column = a.getInt(COLUMN, DEFAULT_COLUMN); 2169 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 2170 float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT); 2171 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight); 2172 2173 int row = a.getInt(ROW, DEFAULT_ROW); 2174 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); 2175 float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT); 2176 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight); 2177 } finally { 2178 a.recycle(); 2179 } 2180 } 2181 2182 /** 2183 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 2184 * See {@link Gravity}. 2185 * 2186 * @param gravity the new gravity value 2187 * 2188 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 2189 */ 2190 public void setGravity(int gravity) { 2191 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); 2192 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); 2193 } 2194 2195 @Override 2196 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 2197 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 2198 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 2199 } 2200 2201 final void setRowSpecSpan(Interval span) { 2202 rowSpec = rowSpec.copyWriteSpan(span); 2203 } 2204 2205 final void setColumnSpecSpan(Interval span) { 2206 columnSpec = columnSpec.copyWriteSpan(span); 2207 } 2208 2209 @Override 2210 public boolean equals(Object o) { 2211 if (this == o) return true; 2212 if (o == null || getClass() != o.getClass()) return false; 2213 2214 LayoutParams that = (LayoutParams) o; 2215 2216 if (!columnSpec.equals(that.columnSpec)) return false; 2217 if (!rowSpec.equals(that.rowSpec)) return false; 2218 2219 return true; 2220 } 2221 2222 @Override 2223 public int hashCode() { 2224 int result = rowSpec.hashCode(); 2225 result = 31 * result + columnSpec.hashCode(); 2226 return result; 2227 } 2228 } 2229 2230 /* 2231 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 2232 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 2233 */ 2234 final static class Arc { 2235 public final Interval span; 2236 public final MutableInt value; 2237 public boolean valid = true; 2238 2239 public Arc(Interval span, MutableInt value) { 2240 this.span = span; 2241 this.value = value; 2242 } 2243 2244 @Override 2245 public String toString() { 2246 return span + " " + (!valid ? "+>" : "->") + " " + value; 2247 } 2248 } 2249 2250 // A mutable Integer - used to avoid heap allocation during the layout operation 2251 2252 final static class MutableInt { 2253 public int value; 2254 2255 public MutableInt() { 2256 reset(); 2257 } 2258 2259 public MutableInt(int value) { 2260 this.value = value; 2261 } 2262 2263 public void reset() { 2264 value = Integer.MIN_VALUE; 2265 } 2266 2267 @Override 2268 public String toString() { 2269 return Integer.toString(value); 2270 } 2271 } 2272 2273 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> { 2274 private final Class<K> keyType; 2275 private final Class<V> valueType; 2276 2277 private Assoc(Class<K> keyType, Class<V> valueType) { 2278 this.keyType = keyType; 2279 this.valueType = valueType; 2280 } 2281 2282 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { 2283 return new Assoc<K, V>(keyType, valueType); 2284 } 2285 2286 public void put(K key, V value) { 2287 add(Pair.create(key, value)); 2288 } 2289 2290 @SuppressWarnings(value = "unchecked") 2291 public PackedMap<K, V> pack() { 2292 int N = size(); 2293 K[] keys = (K[]) Array.newInstance(keyType, N); 2294 V[] values = (V[]) Array.newInstance(valueType, N); 2295 for (int i = 0; i < N; i++) { 2296 keys[i] = get(i).first; 2297 values[i] = get(i).second; 2298 } 2299 return new PackedMap<K, V>(keys, values); 2300 } 2301 } 2302 2303 /* 2304 This data structure is used in place of a Map where we have an index that refers to the order 2305 in which each key/value pairs were added to the map. In this case we store keys and values 2306 in arrays of a length that is equal to the number of unique keys. We also maintain an 2307 array of indexes from insertion order to the compacted arrays of keys and values. 2308 2309 Note that behavior differs from that of a LinkedHashMap in that repeated entries 2310 *do* get added multiples times. So the length of index is equals to the number of 2311 items added. 2312 2313 This is useful in the GridLayout class where we can rely on the order of children not 2314 changing during layout - to use integer-based lookup for our internal structures 2315 rather than using (and storing) an implementation of Map<Key, ?>. 2316 */ 2317 @SuppressWarnings(value = "unchecked") 2318 final static class PackedMap<K, V> { 2319 public final int[] index; 2320 public final K[] keys; 2321 public final V[] values; 2322 2323 private PackedMap(K[] keys, V[] values) { 2324 this.index = createIndex(keys); 2325 2326 this.keys = compact(keys, index); 2327 this.values = compact(values, index); 2328 } 2329 2330 public V getValue(int i) { 2331 return values[index[i]]; 2332 } 2333 2334 private static <K> int[] createIndex(K[] keys) { 2335 int size = keys.length; 2336 int[] result = new int[size]; 2337 2338 Map<K, Integer> keyToIndex = new HashMap<K, Integer>(); 2339 for (int i = 0; i < size; i++) { 2340 K key = keys[i]; 2341 Integer index = keyToIndex.get(key); 2342 if (index == null) { 2343 index = keyToIndex.size(); 2344 keyToIndex.put(key, index); 2345 } 2346 result[i] = index; 2347 } 2348 return result; 2349 } 2350 2351 /* 2352 Create a compact array of keys or values using the supplied index. 2353 */ 2354 private static <K> K[] compact(K[] a, int[] index) { 2355 int size = a.length; 2356 Class<?> componentType = a.getClass().getComponentType(); 2357 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 2358 2359 // this overwrite duplicates, retaining the last equivalent entry 2360 for (int i = 0; i < size; i++) { 2361 result[index[i]] = a[i]; 2362 } 2363 return result; 2364 } 2365 } 2366 2367 /* 2368 For each group (with a given alignment) we need to store the amount of space required 2369 before the alignment point and the amount of space required after it. One side of this 2370 calculation is always 0 for START and END alignments but we don't make use of this. 2371 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 2372 simple optimisations are possible. 2373 2374 The general algorithm therefore is to create a Map (actually a PackedMap) from 2375 group to Bounds and to loop through all Views in the group taking the maximum 2376 of the values for each View. 2377 */ 2378 static class Bounds { 2379 public int before; 2380 public int after; 2381 public int flexibility; // we're flexible iff all included specs are flexible 2382 2383 private Bounds() { 2384 reset(); 2385 } 2386 2387 protected void reset() { 2388 before = Integer.MIN_VALUE; 2389 after = Integer.MIN_VALUE; 2390 flexibility = CAN_STRETCH; // from the above, we're flexible when empty 2391 } 2392 2393 protected void include(int before, int after) { 2394 this.before = max(this.before, before); 2395 this.after = max(this.after, after); 2396 } 2397 2398 protected int size(boolean min) { 2399 if (!min) { 2400 if (canStretch(flexibility)) { 2401 return MAX_SIZE; 2402 } 2403 } 2404 return before + after; 2405 } 2406 2407 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { 2408 return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); 2409 } 2410 2411 protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) { 2412 this.flexibility &= spec.getFlexibility(); 2413 boolean horizontal = axis.horizontal; 2414 Alignment alignment = spec.getAbsoluteAlignment(axis.horizontal); 2415 // todo test this works correctly when the returned value is UNDEFINED 2416 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); 2417 include(before, size - before); 2418 } 2419 2420 @Override 2421 public String toString() { 2422 return "Bounds{" + 2423 "before=" + before + 2424 ", after=" + after + 2425 '}'; 2426 } 2427 } 2428 2429 /** 2430 * An Interval represents a contiguous range of values that lie between 2431 * the interval's {@link #min} and {@link #max} values. 2432 * <p> 2433 * Intervals are immutable so may be passed as values and used as keys in hash tables. 2434 * It is not necessary to have multiple instances of Intervals which have the same 2435 * {@link #min} and {@link #max} values. 2436 * <p> 2437 * Intervals are often written as {@code [min, max]} and represent the set of values 2438 * {@code x} such that {@code min <= x < max}. 2439 */ 2440 final static class Interval { 2441 /** 2442 * The minimum value. 2443 */ 2444 public final int min; 2445 2446 /** 2447 * The maximum value. 2448 */ 2449 public final int max; 2450 2451 /** 2452 * Construct a new Interval, {@code interval}, where: 2453 * <ul> 2454 * <li> {@code interval.min = min} </li> 2455 * <li> {@code interval.max = max} </li> 2456 * </ul> 2457 * 2458 * @param min the minimum value. 2459 * @param max the maximum value. 2460 */ 2461 public Interval(int min, int max) { 2462 this.min = min; 2463 this.max = max; 2464 } 2465 2466 int size() { 2467 return max - min; 2468 } 2469 2470 Interval inverse() { 2471 return new Interval(max, min); 2472 } 2473 2474 /** 2475 * Returns {@code true} if the {@link #getClass class}, 2476 * {@link #min} and {@link #max} properties of this Interval and the 2477 * supplied parameter are pairwise equal; {@code false} otherwise. 2478 * 2479 * @param that the object to compare this interval with 2480 * 2481 * @return {@code true} if the specified object is equal to this 2482 * {@code Interval}, {@code false} otherwise. 2483 */ 2484 @Override 2485 public boolean equals(Object that) { 2486 if (this == that) { 2487 return true; 2488 } 2489 if (that == null || getClass() != that.getClass()) { 2490 return false; 2491 } 2492 2493 Interval interval = (Interval) that; 2494 2495 if (max != interval.max) { 2496 return false; 2497 } 2498 //noinspection RedundantIfStatement 2499 if (min != interval.min) { 2500 return false; 2501 } 2502 2503 return true; 2504 } 2505 2506 @Override 2507 public int hashCode() { 2508 int result = min; 2509 result = 31 * result + max; 2510 return result; 2511 } 2512 2513 @Override 2514 public String toString() { 2515 return "[" + min + ", " + max + "]"; 2516 } 2517 } 2518 2519 /** 2520 * A Spec defines the horizontal or vertical characteristics of a group of 2521 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em> 2522 * along the appropriate axis. 2523 * <p> 2524 * The <em>grid indices</em> are the leading and trailing edges of this cell group. 2525 * See {@link GridLayout} for a description of the conventions used by GridLayout 2526 * for grid indices. 2527 * <p> 2528 * The <em>alignment</em> property specifies how cells should be aligned in this group. 2529 * For row groups, this specifies the vertical alignment. 2530 * For column groups, this specifies the horizontal alignment. 2531 * <p> 2532 * Use the following static methods to create specs: 2533 * <ul> 2534 * <li>{@link #spec(int)}</li> 2535 * <li>{@link #spec(int, int)}</li> 2536 * <li>{@link #spec(int, Alignment)}</li> 2537 * <li>{@link #spec(int, int, Alignment)}</li> 2538 * <li>{@link #spec(int, float)}</li> 2539 * <li>{@link #spec(int, int, float)}</li> 2540 * <li>{@link #spec(int, Alignment, float)}</li> 2541 * <li>{@link #spec(int, int, Alignment, float)}</li> 2542 * </ul> 2543 * 2544 */ 2545 public static class Spec { 2546 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); 2547 static final float DEFAULT_WEIGHT = 0; 2548 2549 final boolean startDefined; 2550 final Interval span; 2551 final Alignment alignment; 2552 final float weight; 2553 2554 private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { 2555 this.startDefined = startDefined; 2556 this.span = span; 2557 this.alignment = alignment; 2558 this.weight = weight; 2559 } 2560 2561 private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { 2562 this(startDefined, new Interval(start, start + size), alignment, weight); 2563 } 2564 2565 public Alignment getAbsoluteAlignment(boolean horizontal) { 2566 if (alignment != UNDEFINED_ALIGNMENT) { 2567 return alignment; 2568 } 2569 if (weight == 0f) { 2570 return horizontal ? START : BASELINE; 2571 } 2572 return FILL; 2573 } 2574 2575 final Spec copyWriteSpan(Interval span) { 2576 return new Spec(startDefined, span, alignment, weight); 2577 } 2578 2579 final Spec copyWriteAlignment(Alignment alignment) { 2580 return new Spec(startDefined, span, alignment, weight); 2581 } 2582 2583 final int getFlexibility() { 2584 return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; 2585 } 2586 2587 /** 2588 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span} 2589 * properties of this Spec and the supplied parameter are pairwise equal, 2590 * {@code false} otherwise. 2591 * 2592 * @param that the object to compare this spec with 2593 * 2594 * @return {@code true} if the specified object is equal to this 2595 * {@code Spec}; {@code false} otherwise 2596 */ 2597 @Override 2598 public boolean equals(Object that) { 2599 if (this == that) { 2600 return true; 2601 } 2602 if (that == null || getClass() != that.getClass()) { 2603 return false; 2604 } 2605 2606 Spec spec = (Spec) that; 2607 2608 if (!alignment.equals(spec.alignment)) { 2609 return false; 2610 } 2611 //noinspection RedundantIfStatement 2612 if (!span.equals(spec.span)) { 2613 return false; 2614 } 2615 2616 return true; 2617 } 2618 2619 @Override 2620 public int hashCode() { 2621 int result = span.hashCode(); 2622 result = 31 * result + alignment.hashCode(); 2623 return result; 2624 } 2625 } 2626 2627 /** 2628 * Return a Spec, {@code spec}, where: 2629 * <ul> 2630 * <li> {@code spec.span = [start, start + size]} </li> 2631 * <li> {@code spec.alignment = alignment} </li> 2632 * <li> {@code spec.weight = weight} </li> 2633 * </ul> 2634 * <p> 2635 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2636 * 2637 * @param start the start 2638 * @param size the size 2639 * @param alignment the alignment 2640 * @param weight the weight 2641 */ 2642 public static Spec spec(int start, int size, Alignment alignment, float weight) { 2643 return new Spec(start != UNDEFINED, start, size, alignment, weight); 2644 } 2645 2646 /** 2647 * Equivalent to: {@code spec(start, 1, alignment, weight)}. 2648 * 2649 * @param start the start 2650 * @param alignment the alignment 2651 * @param weight the weight 2652 */ 2653 public static Spec spec(int start, Alignment alignment, float weight) { 2654 return spec(start, 1, alignment, weight); 2655 } 2656 2657 /** 2658 * Equivalent to: {@code spec(start, 1, default_alignment, weight)} - 2659 * where {@code default_alignment} is specified in 2660 * {@link android.widget.GridLayout.LayoutParams}. 2661 * 2662 * @param start the start 2663 * @param size the size 2664 * @param weight the weight 2665 */ 2666 public static Spec spec(int start, int size, float weight) { 2667 return spec(start, size, UNDEFINED_ALIGNMENT, weight); 2668 } 2669 2670 /** 2671 * Equivalent to: {@code spec(start, 1, weight)}. 2672 * 2673 * @param start the start 2674 * @param weight the weight 2675 */ 2676 public static Spec spec(int start, float weight) { 2677 return spec(start, 1, weight); 2678 } 2679 2680 /** 2681 * Equivalent to: {@code spec(start, size, alignment, 0f)}. 2682 * 2683 * @param start the start 2684 * @param size the size 2685 * @param alignment the alignment 2686 */ 2687 public static Spec spec(int start, int size, Alignment alignment) { 2688 return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); 2689 } 2690 2691 /** 2692 * Return a Spec, {@code spec}, where: 2693 * <ul> 2694 * <li> {@code spec.span = [start, start + 1]} </li> 2695 * <li> {@code spec.alignment = alignment} </li> 2696 * </ul> 2697 * <p> 2698 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2699 * 2700 * @param start the start index 2701 * @param alignment the alignment 2702 * 2703 * @see #spec(int, int, Alignment) 2704 */ 2705 public static Spec spec(int start, Alignment alignment) { 2706 return spec(start, 1, alignment); 2707 } 2708 2709 /** 2710 * Return a Spec, {@code spec}, where: 2711 * <ul> 2712 * <li> {@code spec.span = [start, start + size]} </li> 2713 * </ul> 2714 * <p> 2715 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2716 * 2717 * @param start the start 2718 * @param size the size 2719 * 2720 * @see #spec(int, Alignment) 2721 */ 2722 public static Spec spec(int start, int size) { 2723 return spec(start, size, UNDEFINED_ALIGNMENT); 2724 } 2725 2726 /** 2727 * Return a Spec, {@code spec}, where: 2728 * <ul> 2729 * <li> {@code spec.span = [start, start + 1]} </li> 2730 * </ul> 2731 * <p> 2732 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2733 * 2734 * @param start the start index 2735 * 2736 * @see #spec(int, int) 2737 */ 2738 public static Spec spec(int start) { 2739 return spec(start, 1); 2740 } 2741 2742 /** 2743 * Alignments specify where a view should be placed within a cell group and 2744 * what size it should be. 2745 * <p> 2746 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec} 2747 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an 2748 * {@code alignment}. Overall placement of the view in the cell 2749 * group is specified by the two alignments which act along each axis independently. 2750 * <p> 2751 * The GridLayout class defines the most common alignments used in general layout: 2752 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START}, 2753 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}. 2754 */ 2755 /* 2756 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, 2757 * to return the appropriate value for the type of alignment being defined. 2758 * The enclosing algorithms position the children 2759 * so that the locations defined by the alignment values 2760 * are the same for all of the views in a group. 2761 * <p> 2762 */ 2763 public static abstract class Alignment { 2764 Alignment() { 2765 } 2766 2767 abstract int getGravityOffset(View view, int cellDelta); 2768 2769 /** 2770 * Returns an alignment value. In the case of vertical alignments the value 2771 * returned should indicate the distance from the top of the view to the 2772 * alignment location. 2773 * For horizontal alignments measurement is made from the left edge of the component. 2774 * 2775 * @param view the view to which this alignment should be applied 2776 * @param viewSize the measured size of the view 2777 * @param mode the basis of alignment: CLIP or OPTICAL 2778 * @return the alignment value 2779 */ 2780 abstract int getAlignmentValue(View view, int viewSize, int mode); 2781 2782 /** 2783 * Returns the size of the view specified by this alignment. 2784 * In the case of vertical alignments this method should return a height; for 2785 * horizontal alignments this method should return the width. 2786 * <p> 2787 * The default implementation returns {@code viewSize}. 2788 * 2789 * @param view the view to which this alignment should be applied 2790 * @param viewSize the measured size of the view 2791 * @param cellSize the size of the cell into which this view will be placed 2792 * @return the aligned size 2793 */ 2794 int getSizeInCell(View view, int viewSize, int cellSize) { 2795 return viewSize; 2796 } 2797 2798 Bounds getBounds() { 2799 return new Bounds(); 2800 } 2801 } 2802 2803 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { 2804 @Override 2805 int getGravityOffset(View view, int cellDelta) { 2806 return UNDEFINED; 2807 } 2808 2809 @Override 2810 public int getAlignmentValue(View view, int viewSize, int mode) { 2811 return UNDEFINED; 2812 } 2813 }; 2814 2815 /** 2816 * Indicates that a view should be aligned with the <em>start</em> 2817 * edges of the other views in its cell group. 2818 */ 2819 private static final Alignment LEADING = new Alignment() { 2820 @Override 2821 int getGravityOffset(View view, int cellDelta) { 2822 return 0; 2823 } 2824 2825 @Override 2826 public int getAlignmentValue(View view, int viewSize, int mode) { 2827 return 0; 2828 } 2829 }; 2830 2831 /** 2832 * Indicates that a view should be aligned with the <em>end</em> 2833 * edges of the other views in its cell group. 2834 */ 2835 private static final Alignment TRAILING = new Alignment() { 2836 @Override 2837 int getGravityOffset(View view, int cellDelta) { 2838 return cellDelta; 2839 } 2840 2841 @Override 2842 public int getAlignmentValue(View view, int viewSize, int mode) { 2843 return viewSize; 2844 } 2845 }; 2846 2847 /** 2848 * Indicates that a view should be aligned with the <em>top</em> 2849 * edges of the other views in its cell group. 2850 */ 2851 public static final Alignment TOP = LEADING; 2852 2853 /** 2854 * Indicates that a view should be aligned with the <em>bottom</em> 2855 * edges of the other views in its cell group. 2856 */ 2857 public static final Alignment BOTTOM = TRAILING; 2858 2859 /** 2860 * Indicates that a view should be aligned with the <em>start</em> 2861 * edges of the other views in its cell group. 2862 */ 2863 public static final Alignment START = LEADING; 2864 2865 /** 2866 * Indicates that a view should be aligned with the <em>end</em> 2867 * edges of the other views in its cell group. 2868 */ 2869 public static final Alignment END = TRAILING; 2870 2871 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) { 2872 return new Alignment() { 2873 @Override 2874 int getGravityOffset(View view, int cellDelta) { 2875 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta); 2876 } 2877 2878 @Override 2879 public int getAlignmentValue(View view, int viewSize, int mode) { 2880 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode); 2881 } 2882 }; 2883 } 2884 2885 /** 2886 * Indicates that a view should be aligned with the <em>left</em> 2887 * edges of the other views in its cell group. 2888 */ 2889 public static final Alignment LEFT = createSwitchingAlignment(START, END); 2890 2891 /** 2892 * Indicates that a view should be aligned with the <em>right</em> 2893 * edges of the other views in its cell group. 2894 */ 2895 public static final Alignment RIGHT = createSwitchingAlignment(END, START); 2896 2897 /** 2898 * Indicates that a view should be <em>centered</em> with the other views in its cell group. 2899 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link 2900 * LayoutParams#columnSpec columnSpecs}. 2901 */ 2902 public static final Alignment CENTER = new Alignment() { 2903 @Override 2904 int getGravityOffset(View view, int cellDelta) { 2905 return cellDelta >> 1; 2906 } 2907 2908 @Override 2909 public int getAlignmentValue(View view, int viewSize, int mode) { 2910 return viewSize >> 1; 2911 } 2912 }; 2913 2914 /** 2915 * Indicates that a view should be aligned with the <em>baselines</em> 2916 * of the other views in its cell group. 2917 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}. 2918 * 2919 * @see View#getBaseline() 2920 */ 2921 public static final Alignment BASELINE = new Alignment() { 2922 @Override 2923 int getGravityOffset(View view, int cellDelta) { 2924 return 0; // baseline gravity is top 2925 } 2926 2927 @Override 2928 public int getAlignmentValue(View view, int viewSize, int mode) { 2929 if (view.getVisibility() == GONE) { 2930 return 0; 2931 } 2932 int baseline = view.getBaseline(); 2933 return baseline == -1 ? UNDEFINED : baseline; 2934 } 2935 2936 @Override 2937 public Bounds getBounds() { 2938 return new Bounds() { 2939 /* 2940 In a baseline aligned row in which some components define a baseline 2941 and some don't, we need a third variable to properly account for all 2942 the sizes. This tracks the maximum size of all the components - 2943 including those that don't define a baseline. 2944 */ 2945 private int size; 2946 2947 @Override 2948 protected void reset() { 2949 super.reset(); 2950 size = Integer.MIN_VALUE; 2951 } 2952 2953 @Override 2954 protected void include(int before, int after) { 2955 super.include(before, after); 2956 size = max(size, before + after); 2957 } 2958 2959 @Override 2960 protected int size(boolean min) { 2961 return max(super.size(min), size); 2962 } 2963 2964 @Override 2965 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) { 2966 return max(0, super.getOffset(gl, c, a, size, hrz)); 2967 } 2968 }; 2969 } 2970 }; 2971 2972 /** 2973 * Indicates that a view should expanded to fit the boundaries of its cell group. 2974 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and 2975 * {@link LayoutParams#columnSpec columnSpecs}. 2976 */ 2977 public static final Alignment FILL = new Alignment() { 2978 @Override 2979 int getGravityOffset(View view, int cellDelta) { 2980 return 0; 2981 } 2982 2983 @Override 2984 public int getAlignmentValue(View view, int viewSize, int mode) { 2985 return UNDEFINED; 2986 } 2987 2988 @Override 2989 public int getSizeInCell(View view, int viewSize, int cellSize) { 2990 return cellSize; 2991 } 2992 }; 2993 2994 static boolean canStretch(int flexibility) { 2995 return (flexibility & CAN_STRETCH) != 0; 2996 } 2997 2998 private static final int INFLEXIBLE = 0; 2999 private static final int CAN_STRETCH = 2; 3000} 3001