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