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