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 void solve(Arc[] arcs, int[] locations) { 1617 String axisName = horizontal ? "horizontal" : "vertical"; 1618 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. 1619 boolean[] originalCulprits = null; 1620 1621 for (int p = 0; p < arcs.length; p++) { 1622 init(locations); 1623 1624 // We take one extra pass over traditional Bellman-Ford (and omit their final step) 1625 for (int i = 0; i < N; i++) { 1626 boolean changed = false; 1627 for (int j = 0, length = arcs.length; j < length; j++) { 1628 changed |= relax(locations, arcs[j]); 1629 } 1630 if (!changed) { 1631 if (originalCulprits != null) { 1632 logError(axisName, arcs, originalCulprits); 1633 } 1634 return; 1635 } 1636 } 1637 1638 boolean[] culprits = new boolean[arcs.length]; 1639 for (int i = 0; i < N; i++) { 1640 for (int j = 0, length = arcs.length; j < length; j++) { 1641 culprits[j] |= relax(locations, arcs[j]); 1642 } 1643 } 1644 1645 if (p == 0) { 1646 originalCulprits = culprits; 1647 } 1648 1649 for (int i = 0; i < arcs.length; i++) { 1650 if (culprits[i]) { 1651 Arc arc = arcs[i]; 1652 // Only remove max values, min values alone cannot be inconsistent 1653 if (arc.span.min < arc.span.max) { 1654 continue; 1655 } 1656 arc.valid = false; 1657 break; 1658 } 1659 } 1660 } 1661 } 1662 1663 private void computeMargins(boolean leading) { 1664 int[] margins = leading ? leadingMargins : trailingMargins; 1665 for (int i = 0, N = getChildCount(); i < N; i++) { 1666 View c = getChildAt(i); 1667 if (c.getVisibility() == View.GONE) continue; 1668 LayoutParams lp = getLayoutParams(c); 1669 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1670 Interval span = spec.span; 1671 int index = leading ? span.min : span.max; 1672 margins[index] = max(margins[index], getMargin1(c, horizontal, leading)); 1673 } 1674 } 1675 1676 // External entry points 1677 1678 public int[] getLeadingMargins() { 1679 if (leadingMargins == null) { 1680 leadingMargins = new int[getCount() + 1]; 1681 } 1682 if (!leadingMarginsValid) { 1683 computeMargins(true); 1684 leadingMarginsValid = true; 1685 } 1686 return leadingMargins; 1687 } 1688 1689 public int[] getTrailingMargins() { 1690 if (trailingMargins == null) { 1691 trailingMargins = new int[getCount() + 1]; 1692 } 1693 if (!trailingMarginsValid) { 1694 computeMargins(false); 1695 trailingMarginsValid = true; 1696 } 1697 return trailingMargins; 1698 } 1699 1700 private void solve(int[] a) { 1701 solve(getArcs(), a); 1702 } 1703 1704 private boolean computeHasWeights() { 1705 for (int i = 0, N = getChildCount(); i < N; i++) { 1706 LayoutParams lp = getLayoutParams(getChildAt(i)); 1707 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1708 if (spec.weight != 0) { 1709 return true; 1710 } 1711 } 1712 return false; 1713 } 1714 1715 private boolean hasWeights() { 1716 if (!hasWeightsValid) { 1717 hasWeights = computeHasWeights(); 1718 hasWeightsValid = true; 1719 } 1720 return hasWeights; 1721 } 1722 1723 public int[] getOriginalMeasurements() { 1724 if (originalMeasurements == null) { 1725 originalMeasurements = new int[getChildCount()]; 1726 } 1727 return originalMeasurements; 1728 } 1729 1730 private void recordOriginalMeasurement(int i) { 1731 if (hasWeights()) { 1732 getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal); 1733 } 1734 } 1735 1736 public int[] getDeltas() { 1737 if (deltas == null) { 1738 deltas = new int[getChildCount()]; 1739 } 1740 return deltas; 1741 } 1742 1743 private void shareOutDelta() { 1744 int totalDelta = 0; 1745 float totalWeight = 0; 1746 for (int i = 0, N = getChildCount(); i < N; i++) { 1747 View c = getChildAt(i); 1748 LayoutParams lp = getLayoutParams(c); 1749 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1750 float weight = spec.weight; 1751 if (weight != 0) { 1752 int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i]; 1753 totalDelta += delta; 1754 totalWeight += weight; 1755 } 1756 } 1757 for (int i = 0, N = getChildCount(); i < N; i++) { 1758 LayoutParams lp = getLayoutParams(getChildAt(i)); 1759 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1760 float weight = spec.weight; 1761 if (weight != 0) { 1762 int delta = Math.round((weight * totalDelta / totalWeight)); 1763 deltas[i] = delta; 1764 // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end 1765 totalDelta -= delta; 1766 totalWeight -= weight; 1767 } 1768 } 1769 } 1770 1771 private void solveAndDistributeSpace(int[] a) { 1772 Arrays.fill(getDeltas(), 0); 1773 solve(a); 1774 shareOutDelta(); 1775 arcsValid = false; 1776 forwardLinksValid = false; 1777 backwardLinksValid = false; 1778 groupBoundsValid = false; 1779 solve(a); 1780 } 1781 1782 private void computeLocations(int[] a) { 1783 if (!hasWeights()) { 1784 solve(a); 1785 } else { 1786 solveAndDistributeSpace(a); 1787 } 1788 if (!orderPreserved) { 1789 // Solve returns the smallest solution to the constraint system for which all 1790 // values are positive. One value is therefore zero - though if the row/col 1791 // order is not preserved this may not be the first vertex. For consistency, 1792 // translate all the values so that they measure the distance from a[0]; the 1793 // leading edge of the parent. After this transformation some values may be 1794 // negative. 1795 int a0 = a[0]; 1796 for (int i = 0, N = a.length; i < N; i++) { 1797 a[i] = a[i] - a0; 1798 } 1799 } 1800 } 1801 1802 public int[] getLocations() { 1803 if (locations == null) { 1804 int N = getCount() + 1; 1805 locations = new int[N]; 1806 } 1807 if (!locationsValid) { 1808 computeLocations(locations); 1809 locationsValid = true; 1810 } 1811 return locations; 1812 } 1813 1814 private int size(int[] locations) { 1815 // The parental edges are attached to vertices 0 and N - even when order is not 1816 // being preserved and other vertices fall outside this range. Measure the distance 1817 // between vertices 0 and N, assuming that locations[0] = 0. 1818 return locations[getCount()]; 1819 } 1820 1821 private void setParentConstraints(int min, int max) { 1822 parentMin.value = min; 1823 parentMax.value = -max; 1824 locationsValid = false; 1825 } 1826 1827 private int getMeasure(int min, int max) { 1828 setParentConstraints(min, max); 1829 return size(getLocations()); 1830 } 1831 1832 public int getMeasure(int measureSpec) { 1833 int mode = MeasureSpec.getMode(measureSpec); 1834 int size = MeasureSpec.getSize(measureSpec); 1835 switch (mode) { 1836 case MeasureSpec.UNSPECIFIED: { 1837 return getMeasure(0, MAX_SIZE); 1838 } 1839 case MeasureSpec.EXACTLY: { 1840 return getMeasure(size, size); 1841 } 1842 case MeasureSpec.AT_MOST: { 1843 return getMeasure(0, size); 1844 } 1845 default: { 1846 assert false; 1847 return 0; 1848 } 1849 } 1850 } 1851 1852 public void layout(int size) { 1853 setParentConstraints(size, size); 1854 getLocations(); 1855 } 1856 1857 public void invalidateStructure() { 1858 maxIndex = UNDEFINED; 1859 1860 groupBounds = null; 1861 forwardLinks = null; 1862 backwardLinks = null; 1863 1864 leadingMargins = null; 1865 trailingMargins = null; 1866 arcs = null; 1867 1868 locations = null; 1869 1870 originalMeasurements = null; 1871 deltas = null; 1872 hasWeightsValid = false; 1873 1874 invalidateValues(); 1875 } 1876 1877 public void invalidateValues() { 1878 groupBoundsValid = false; 1879 forwardLinksValid = false; 1880 backwardLinksValid = false; 1881 1882 leadingMarginsValid = false; 1883 trailingMarginsValid = false; 1884 arcsValid = false; 1885 1886 locationsValid = false; 1887 } 1888 } 1889 1890 /** 1891 * Layout information associated with each of the children of a GridLayout. 1892 * <p> 1893 * GridLayout supports both row and column spanning and arbitrary forms of alignment within 1894 * each cell group. The fundamental parameters associated with each cell group are 1895 * gathered into their vertical and horizontal components and stored 1896 * in the {@link #rowSpec} and {@link #columnSpec} layout parameters. 1897 * {@link GridLayout.Spec Specs} are immutable structures 1898 * and may be shared between the layout parameters of different children. 1899 * <p> 1900 * The row and column specs contain the leading and trailing indices along each axis 1901 * and together specify the four grid indices that delimit the cells of this cell group. 1902 * <p> 1903 * The alignment properties of the row and column specs together specify 1904 * both aspects of alignment within the cell group. It is also possible to specify a child's 1905 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} 1906 * method. 1907 * <p> 1908 * The weight property is also included in Spec and specifies the proportion of any 1909 * excess space that is due to the associated view. 1910 * 1911 * <h4>WRAP_CONTENT and MATCH_PARENT</h4> 1912 * 1913 * Because the default values of the {@link #width} and {@link #height} 1914 * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly 1915 * declared in the layout parameters of GridLayout's children. In addition, 1916 * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from 1917 * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is 1918 * instead controlled by the principle of <em>flexibility</em>, 1919 * as discussed in {@link GridLayout}. 1920 * 1921 * <h4>Summary</h4> 1922 * 1923 * You should not need to use either of the special size values: 1924 * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of 1925 * a GridLayout. 1926 * 1927 * <h4>Default values</h4> 1928 * 1929 * <ul> 1930 * <li>{@link #width} = {@link #WRAP_CONTENT}</li> 1931 * <li>{@link #height} = {@link #WRAP_CONTENT}</li> 1932 * <li>{@link #topMargin} = 0 when 1933 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1934 * {@code false}; otherwise {@link #UNDEFINED}, to 1935 * indicate that a default value should be computed on demand. </li> 1936 * <li>{@link #leftMargin} = 0 when 1937 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1938 * {@code false}; otherwise {@link #UNDEFINED}, to 1939 * indicate that a default value should be computed on demand. </li> 1940 * <li>{@link #bottomMargin} = 0 when 1941 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1942 * {@code false}; otherwise {@link #UNDEFINED}, to 1943 * indicate that a default value should be computed on demand. </li> 1944 * <li>{@link #rightMargin} = 0 when 1945 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1946 * {@code false}; otherwise {@link #UNDEFINED}, to 1947 * indicate that a default value should be computed on demand. </li> 1948 * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> 1949 * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> 1950 * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> 1951 * <li>{@link #rowSpec}<code>.weight</code> = 0 </li> 1952 * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> 1953 * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> 1954 * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> 1955 * <li>{@link #columnSpec}<code>.weight</code> = 0 </li> 1956 * </ul> 1957 * 1958 * See {@link GridLayout} for a more complete description of the conventions 1959 * used by GridLayout in the interpretation of the properties of this class. 1960 * 1961 * @attr ref android.R.styleable#GridLayout_Layout_layout_row 1962 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan 1963 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight 1964 * @attr ref android.R.styleable#GridLayout_Layout_layout_column 1965 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan 1966 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight 1967 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1968 */ 1969 public static class LayoutParams extends MarginLayoutParams { 1970 1971 // Default values 1972 1973 private static final int DEFAULT_WIDTH = WRAP_CONTENT; 1974 private static final int DEFAULT_HEIGHT = WRAP_CONTENT; 1975 private static final int DEFAULT_MARGIN = UNDEFINED; 1976 private static final int DEFAULT_ROW = UNDEFINED; 1977 private static final int DEFAULT_COLUMN = UNDEFINED; 1978 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); 1979 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); 1980 1981 // TypedArray indices 1982 1983 private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_layout_margin; 1984 private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginLeft; 1985 private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_layout_marginTop; 1986 private static final int RIGHT_MARGIN = 1987 R.styleable.ViewGroup_MarginLayout_layout_marginRight; 1988 private static final int BOTTOM_MARGIN = 1989 R.styleable.ViewGroup_MarginLayout_layout_marginBottom; 1990 1991 private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; 1992 private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; 1993 private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight; 1994 1995 private static final int ROW = R.styleable.GridLayout_Layout_layout_row; 1996 private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; 1997 private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight; 1998 1999 private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; 2000 2001 // Instance variables 2002 2003 /** 2004 * The spec that defines the vertical characteristics of the cell group 2005 * described by these layout parameters. 2006 * If an assignment is made to this field after a measurement or layout operation 2007 * has already taken place, a call to 2008 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 2009 * must be made to notify GridLayout of the change. GridLayout is normally able 2010 * to detect when code fails to observe this rule, issue a warning and take steps to 2011 * compensate for the omission. This facility is implemented on a best effort basis 2012 * and should not be relied upon in production code - so it is best to include the above 2013 * calls to remove the warnings as soon as it is practical. 2014 */ 2015 public Spec rowSpec = Spec.UNDEFINED; 2016 2017 /** 2018 * The spec that defines the horizontal characteristics of the cell group 2019 * described by these layout parameters. 2020 * If an assignment is made to this field after a measurement or layout operation 2021 * has already taken place, a call to 2022 * {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)} 2023 * must be made to notify GridLayout of the change. GridLayout is normally able 2024 * to detect when code fails to observe this rule, issue a warning and take steps to 2025 * compensate for the omission. This facility is implemented on a best effort basis 2026 * and should not be relied upon in production code - so it is best to include the above 2027 * calls to remove the warnings as soon as it is practical. 2028 */ 2029 public Spec columnSpec = Spec.UNDEFINED; 2030 2031 // Constructors 2032 2033 private LayoutParams( 2034 int width, int height, 2035 int left, int top, int right, int bottom, 2036 Spec rowSpec, Spec columnSpec) { 2037 super(width, height); 2038 setMargins(left, top, right, bottom); 2039 this.rowSpec = rowSpec; 2040 this.columnSpec = columnSpec; 2041 } 2042 2043 /** 2044 * Constructs a new LayoutParams instance for this <code>rowSpec</code> 2045 * and <code>columnSpec</code>. All other fields are initialized with 2046 * default values as defined in {@link LayoutParams}. 2047 * 2048 * @param rowSpec the rowSpec 2049 * @param columnSpec the columnSpec 2050 */ 2051 public LayoutParams(Spec rowSpec, Spec columnSpec) { 2052 this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 2053 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 2054 rowSpec, columnSpec); 2055 } 2056 2057 /** 2058 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 2059 */ 2060 public LayoutParams() { 2061 this(Spec.UNDEFINED, Spec.UNDEFINED); 2062 } 2063 2064 // Copying constructors 2065 2066 /** 2067 * {@inheritDoc} 2068 */ 2069 public LayoutParams(ViewGroup.LayoutParams params) { 2070 super(params); 2071 } 2072 2073 /** 2074 * {@inheritDoc} 2075 */ 2076 public LayoutParams(MarginLayoutParams params) { 2077 super(params); 2078 } 2079 2080 /** 2081 * Copy constructor. Clones the width, height, margin values, row spec, 2082 * and column spec of the source. 2083 * 2084 * @param source The layout params to copy from. 2085 */ 2086 public LayoutParams(LayoutParams source) { 2087 super(source); 2088 2089 this.rowSpec = source.rowSpec; 2090 this.columnSpec = source.columnSpec; 2091 } 2092 2093 // AttributeSet constructors 2094 2095 /** 2096 * {@inheritDoc} 2097 * 2098 * Values not defined in the attribute set take the default values 2099 * defined in {@link LayoutParams}. 2100 */ 2101 public LayoutParams(Context context, AttributeSet attrs) { 2102 super(context, attrs); 2103 reInitSuper(context, attrs); 2104 init(context, attrs); 2105 } 2106 2107 // Implementation 2108 2109 // Reinitialise the margins using a different default policy than MarginLayoutParams. 2110 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 2111 // so that a layout manager default can be accessed post set up. We need this as, at the 2112 // point of installation, we do not know how many rows/cols there are and therefore 2113 // which elements are positioned next to the container's trailing edges. We need to 2114 // know this as margins around the container's boundary should have different 2115 // defaults to those between peers. 2116 2117 // This method could be parametrized and moved into MarginLayout. 2118 private void reInitSuper(Context context, AttributeSet attrs) { 2119 TypedArray a = 2120 context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 2121 try { 2122 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 2123 2124 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 2125 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 2126 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 2127 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 2128 } finally { 2129 a.recycle(); 2130 } 2131 } 2132 2133 private void init(Context context, AttributeSet attrs) { 2134 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); 2135 try { 2136 int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); 2137 2138 int column = a.getInt(COLUMN, DEFAULT_COLUMN); 2139 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 2140 float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT); 2141 this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight); 2142 2143 int row = a.getInt(ROW, DEFAULT_ROW); 2144 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); 2145 float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT); 2146 this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight); 2147 } finally { 2148 a.recycle(); 2149 } 2150 } 2151 2152 /** 2153 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 2154 * See {@link Gravity}. 2155 * 2156 * @param gravity the new gravity value 2157 * 2158 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 2159 */ 2160 public void setGravity(int gravity) { 2161 rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); 2162 columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); 2163 } 2164 2165 @Override 2166 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 2167 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 2168 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 2169 } 2170 2171 final void setRowSpecSpan(Interval span) { 2172 rowSpec = rowSpec.copyWriteSpan(span); 2173 } 2174 2175 final void setColumnSpecSpan(Interval span) { 2176 columnSpec = columnSpec.copyWriteSpan(span); 2177 } 2178 2179 @Override 2180 public boolean equals(Object o) { 2181 if (this == o) return true; 2182 if (o == null || getClass() != o.getClass()) return false; 2183 2184 LayoutParams that = (LayoutParams) o; 2185 2186 if (!columnSpec.equals(that.columnSpec)) return false; 2187 if (!rowSpec.equals(that.rowSpec)) return false; 2188 2189 return true; 2190 } 2191 2192 @Override 2193 public int hashCode() { 2194 int result = rowSpec.hashCode(); 2195 result = 31 * result + columnSpec.hashCode(); 2196 return result; 2197 } 2198 } 2199 2200 /* 2201 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 2202 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 2203 */ 2204 final static class Arc { 2205 public final Interval span; 2206 public final MutableInt value; 2207 public boolean valid = true; 2208 2209 public Arc(Interval span, MutableInt value) { 2210 this.span = span; 2211 this.value = value; 2212 } 2213 2214 @Override 2215 public String toString() { 2216 return span + " " + (!valid ? "+>" : "->") + " " + value; 2217 } 2218 } 2219 2220 // A mutable Integer - used to avoid heap allocation during the layout operation 2221 2222 final static class MutableInt { 2223 public int value; 2224 2225 public MutableInt() { 2226 reset(); 2227 } 2228 2229 public MutableInt(int value) { 2230 this.value = value; 2231 } 2232 2233 public void reset() { 2234 value = Integer.MIN_VALUE; 2235 } 2236 2237 @Override 2238 public String toString() { 2239 return Integer.toString(value); 2240 } 2241 } 2242 2243 final static class Assoc<K, V> extends ArrayList<Pair<K, V>> { 2244 private final Class<K> keyType; 2245 private final Class<V> valueType; 2246 2247 private Assoc(Class<K> keyType, Class<V> valueType) { 2248 this.keyType = keyType; 2249 this.valueType = valueType; 2250 } 2251 2252 public static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { 2253 return new Assoc<K, V>(keyType, valueType); 2254 } 2255 2256 public void put(K key, V value) { 2257 add(Pair.create(key, value)); 2258 } 2259 2260 @SuppressWarnings(value = "unchecked") 2261 public PackedMap<K, V> pack() { 2262 int N = size(); 2263 K[] keys = (K[]) Array.newInstance(keyType, N); 2264 V[] values = (V[]) Array.newInstance(valueType, N); 2265 for (int i = 0; i < N; i++) { 2266 keys[i] = get(i).first; 2267 values[i] = get(i).second; 2268 } 2269 return new PackedMap<K, V>(keys, values); 2270 } 2271 } 2272 2273 /* 2274 This data structure is used in place of a Map where we have an index that refers to the order 2275 in which each key/value pairs were added to the map. In this case we store keys and values 2276 in arrays of a length that is equal to the number of unique keys. We also maintain an 2277 array of indexes from insertion order to the compacted arrays of keys and values. 2278 2279 Note that behavior differs from that of a LinkedHashMap in that repeated entries 2280 *do* get added multiples times. So the length of index is equals to the number of 2281 items added. 2282 2283 This is useful in the GridLayout class where we can rely on the order of children not 2284 changing during layout - to use integer-based lookup for our internal structures 2285 rather than using (and storing) an implementation of Map<Key, ?>. 2286 */ 2287 @SuppressWarnings(value = "unchecked") 2288 final static class PackedMap<K, V> { 2289 public final int[] index; 2290 public final K[] keys; 2291 public final V[] values; 2292 2293 private PackedMap(K[] keys, V[] values) { 2294 this.index = createIndex(keys); 2295 2296 this.keys = compact(keys, index); 2297 this.values = compact(values, index); 2298 } 2299 2300 public V getValue(int i) { 2301 return values[index[i]]; 2302 } 2303 2304 private static <K> int[] createIndex(K[] keys) { 2305 int size = keys.length; 2306 int[] result = new int[size]; 2307 2308 Map<K, Integer> keyToIndex = new HashMap<K, Integer>(); 2309 for (int i = 0; i < size; i++) { 2310 K key = keys[i]; 2311 Integer index = keyToIndex.get(key); 2312 if (index == null) { 2313 index = keyToIndex.size(); 2314 keyToIndex.put(key, index); 2315 } 2316 result[i] = index; 2317 } 2318 return result; 2319 } 2320 2321 /* 2322 Create a compact array of keys or values using the supplied index. 2323 */ 2324 private static <K> K[] compact(K[] a, int[] index) { 2325 int size = a.length; 2326 Class<?> componentType = a.getClass().getComponentType(); 2327 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 2328 2329 // this overwrite duplicates, retaining the last equivalent entry 2330 for (int i = 0; i < size; i++) { 2331 result[index[i]] = a[i]; 2332 } 2333 return result; 2334 } 2335 } 2336 2337 /* 2338 For each group (with a given alignment) we need to store the amount of space required 2339 before the alignment point and the amount of space required after it. One side of this 2340 calculation is always 0 for START and END alignments but we don't make use of this. 2341 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 2342 simple optimisations are possible. 2343 2344 The general algorithm therefore is to create a Map (actually a PackedMap) from 2345 group to Bounds and to loop through all Views in the group taking the maximum 2346 of the values for each View. 2347 */ 2348 static class Bounds { 2349 public int before; 2350 public int after; 2351 public int flexibility; // we're flexible iff all included specs are flexible 2352 2353 private Bounds() { 2354 reset(); 2355 } 2356 2357 protected void reset() { 2358 before = Integer.MIN_VALUE; 2359 after = Integer.MIN_VALUE; 2360 flexibility = CAN_STRETCH; // from the above, we're flexible when empty 2361 } 2362 2363 protected void include(int before, int after) { 2364 this.before = max(this.before, before); 2365 this.after = max(this.after, after); 2366 } 2367 2368 protected int size(boolean min) { 2369 if (!min) { 2370 if (canStretch(flexibility)) { 2371 return MAX_SIZE; 2372 } 2373 } 2374 return before + after; 2375 } 2376 2377 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { 2378 return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); 2379 } 2380 2381 protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) { 2382 this.flexibility &= spec.getFlexibility(); 2383 boolean horizontal = axis.horizontal; 2384 Alignment alignment = gl.getAlignment(spec.alignment, horizontal); 2385 // todo test this works correctly when the returned value is UNDEFINED 2386 int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); 2387 include(before, size - before); 2388 } 2389 2390 @Override 2391 public String toString() { 2392 return "Bounds{" + 2393 "before=" + before + 2394 ", after=" + after + 2395 '}'; 2396 } 2397 } 2398 2399 /** 2400 * An Interval represents a contiguous range of values that lie between 2401 * the interval's {@link #min} and {@link #max} values. 2402 * <p> 2403 * Intervals are immutable so may be passed as values and used as keys in hash tables. 2404 * It is not necessary to have multiple instances of Intervals which have the same 2405 * {@link #min} and {@link #max} values. 2406 * <p> 2407 * Intervals are often written as {@code [min, max]} and represent the set of values 2408 * {@code x} such that {@code min <= x < max}. 2409 */ 2410 final static class Interval { 2411 /** 2412 * The minimum value. 2413 */ 2414 public final int min; 2415 2416 /** 2417 * The maximum value. 2418 */ 2419 public final int max; 2420 2421 /** 2422 * Construct a new Interval, {@code interval}, where: 2423 * <ul> 2424 * <li> {@code interval.min = min} </li> 2425 * <li> {@code interval.max = max} </li> 2426 * </ul> 2427 * 2428 * @param min the minimum value. 2429 * @param max the maximum value. 2430 */ 2431 public Interval(int min, int max) { 2432 this.min = min; 2433 this.max = max; 2434 } 2435 2436 int size() { 2437 return max - min; 2438 } 2439 2440 Interval inverse() { 2441 return new Interval(max, min); 2442 } 2443 2444 /** 2445 * Returns {@code true} if the {@link #getClass class}, 2446 * {@link #min} and {@link #max} properties of this Interval and the 2447 * supplied parameter are pairwise equal; {@code false} otherwise. 2448 * 2449 * @param that the object to compare this interval with 2450 * 2451 * @return {@code true} if the specified object is equal to this 2452 * {@code Interval}, {@code false} otherwise. 2453 */ 2454 @Override 2455 public boolean equals(Object that) { 2456 if (this == that) { 2457 return true; 2458 } 2459 if (that == null || getClass() != that.getClass()) { 2460 return false; 2461 } 2462 2463 Interval interval = (Interval) that; 2464 2465 if (max != interval.max) { 2466 return false; 2467 } 2468 //noinspection RedundantIfStatement 2469 if (min != interval.min) { 2470 return false; 2471 } 2472 2473 return true; 2474 } 2475 2476 @Override 2477 public int hashCode() { 2478 int result = min; 2479 result = 31 * result + max; 2480 return result; 2481 } 2482 2483 @Override 2484 public String toString() { 2485 return "[" + min + ", " + max + "]"; 2486 } 2487 } 2488 2489 /** 2490 * A Spec defines the horizontal or vertical characteristics of a group of 2491 * cells. Each spec. defines the <em>grid indices</em> and <em>alignment</em> 2492 * along the appropriate axis. 2493 * <p> 2494 * The <em>grid indices</em> are the leading and trailing edges of this cell group. 2495 * See {@link GridLayout} for a description of the conventions used by GridLayout 2496 * for grid indices. 2497 * <p> 2498 * The <em>alignment</em> property specifies how cells should be aligned in this group. 2499 * For row groups, this specifies the vertical alignment. 2500 * For column groups, this specifies the horizontal alignment. 2501 * <p> 2502 * Use the following static methods to create specs: 2503 * <ul> 2504 * <li>{@link #spec(int)}</li> 2505 * <li>{@link #spec(int, int)}</li> 2506 * <li>{@link #spec(int, Alignment)}</li> 2507 * <li>{@link #spec(int, int, Alignment)}</li> 2508 * <li>{@link #spec(int, float)}</li> 2509 * <li>{@link #spec(int, int, float)}</li> 2510 * <li>{@link #spec(int, Alignment, float)}</li> 2511 * <li>{@link #spec(int, int, Alignment, float)}</li> 2512 * </ul> 2513 * 2514 */ 2515 public static class Spec { 2516 static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); 2517 static final float DEFAULT_WEIGHT = 0; 2518 2519 final boolean startDefined; 2520 final Interval span; 2521 final Alignment alignment; 2522 final float weight; 2523 2524 private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { 2525 this.startDefined = startDefined; 2526 this.span = span; 2527 this.alignment = alignment; 2528 this.weight = weight; 2529 } 2530 2531 private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { 2532 this(startDefined, new Interval(start, start + size), alignment, weight); 2533 } 2534 2535 final Spec copyWriteSpan(Interval span) { 2536 return new Spec(startDefined, span, alignment, weight); 2537 } 2538 2539 final Spec copyWriteAlignment(Alignment alignment) { 2540 return new Spec(startDefined, span, alignment, weight); 2541 } 2542 2543 final int getFlexibility() { 2544 return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; 2545 } 2546 2547 /** 2548 * Returns {@code true} if the {@code class}, {@code alignment} and {@code span} 2549 * properties of this Spec and the supplied parameter are pairwise equal, 2550 * {@code false} otherwise. 2551 * 2552 * @param that the object to compare this spec with 2553 * 2554 * @return {@code true} if the specified object is equal to this 2555 * {@code Spec}; {@code false} otherwise 2556 */ 2557 @Override 2558 public boolean equals(Object that) { 2559 if (this == that) { 2560 return true; 2561 } 2562 if (that == null || getClass() != that.getClass()) { 2563 return false; 2564 } 2565 2566 Spec spec = (Spec) that; 2567 2568 if (!alignment.equals(spec.alignment)) { 2569 return false; 2570 } 2571 //noinspection RedundantIfStatement 2572 if (!span.equals(spec.span)) { 2573 return false; 2574 } 2575 2576 return true; 2577 } 2578 2579 @Override 2580 public int hashCode() { 2581 int result = span.hashCode(); 2582 result = 31 * result + alignment.hashCode(); 2583 return result; 2584 } 2585 } 2586 2587 /** 2588 * Return a Spec, {@code spec}, where: 2589 * <ul> 2590 * <li> {@code spec.span = [start, start + size]} </li> 2591 * <li> {@code spec.alignment = alignment} </li> 2592 * <li> {@code spec.weight = weight} </li> 2593 * </ul> 2594 * <p> 2595 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2596 * 2597 * @param start the start 2598 * @param size the size 2599 * @param alignment the alignment 2600 * @param weight the weight 2601 */ 2602 public static Spec spec(int start, int size, Alignment alignment, float weight) { 2603 return new Spec(start != UNDEFINED, start, size, alignment, weight); 2604 } 2605 2606 /** 2607 * Equivalent to: {@code spec(start, 1, alignment, weight)}. 2608 * 2609 * @param start the start 2610 * @param alignment the alignment 2611 * @param weight the weight 2612 */ 2613 public static Spec spec(int start, Alignment alignment, float weight) { 2614 return spec(start, 1, alignment, weight); 2615 } 2616 2617 /** 2618 * Equivalent to: {@code spec(start, 1, default_alignment, weight)} - 2619 * where {@code default_alignment} is specified in 2620 * {@link android.widget.GridLayout.LayoutParams}. 2621 * 2622 * @param start the start 2623 * @param size the size 2624 * @param weight the weight 2625 */ 2626 public static Spec spec(int start, int size, float weight) { 2627 return spec(start, size, UNDEFINED_ALIGNMENT, weight); 2628 } 2629 2630 /** 2631 * Equivalent to: {@code spec(start, 1, weight)}. 2632 * 2633 * @param start the start 2634 * @param weight the weight 2635 */ 2636 public static Spec spec(int start, float weight) { 2637 return spec(start, 1, weight); 2638 } 2639 2640 /** 2641 * Equivalent to: {@code spec(start, size, alignment, 0f)}. 2642 * 2643 * @param start the start 2644 * @param size the size 2645 * @param alignment the alignment 2646 */ 2647 public static Spec spec(int start, int size, Alignment alignment) { 2648 return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); 2649 } 2650 2651 /** 2652 * Return a Spec, {@code spec}, where: 2653 * <ul> 2654 * <li> {@code spec.span = [start, start + 1]} </li> 2655 * <li> {@code spec.alignment = alignment} </li> 2656 * </ul> 2657 * <p> 2658 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2659 * 2660 * @param start the start index 2661 * @param alignment the alignment 2662 * 2663 * @see #spec(int, int, Alignment) 2664 */ 2665 public static Spec spec(int start, Alignment alignment) { 2666 return spec(start, 1, alignment); 2667 } 2668 2669 /** 2670 * Return a Spec, {@code spec}, where: 2671 * <ul> 2672 * <li> {@code spec.span = [start, start + size]} </li> 2673 * </ul> 2674 * <p> 2675 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2676 * 2677 * @param start the start 2678 * @param size the size 2679 * 2680 * @see #spec(int, Alignment) 2681 */ 2682 public static Spec spec(int start, int size) { 2683 return spec(start, size, UNDEFINED_ALIGNMENT); 2684 } 2685 2686 /** 2687 * Return a Spec, {@code spec}, where: 2688 * <ul> 2689 * <li> {@code spec.span = [start, start + 1]} </li> 2690 * </ul> 2691 * <p> 2692 * To leave the start index undefined, use the value {@link #UNDEFINED}. 2693 * 2694 * @param start the start index 2695 * 2696 * @see #spec(int, int) 2697 */ 2698 public static Spec spec(int start) { 2699 return spec(start, 1); 2700 } 2701 2702 /** 2703 * Alignments specify where a view should be placed within a cell group and 2704 * what size it should be. 2705 * <p> 2706 * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec} 2707 * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an 2708 * {@code alignment}. Overall placement of the view in the cell 2709 * group is specified by the two alignments which act along each axis independently. 2710 * <p> 2711 * The GridLayout class defines the most common alignments used in general layout: 2712 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START}, 2713 * {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}. 2714 */ 2715 /* 2716 * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, 2717 * to return the appropriate value for the type of alignment being defined. 2718 * The enclosing algorithms position the children 2719 * so that the locations defined by the alignment values 2720 * are the same for all of the views in a group. 2721 * <p> 2722 */ 2723 public static abstract class Alignment { 2724 Alignment() { 2725 } 2726 2727 abstract int getGravityOffset(View view, int cellDelta); 2728 2729 /** 2730 * Returns an alignment value. In the case of vertical alignments the value 2731 * returned should indicate the distance from the top of the view to the 2732 * alignment location. 2733 * For horizontal alignments measurement is made from the left edge of the component. 2734 * 2735 * @param view the view to which this alignment should be applied 2736 * @param viewSize the measured size of the view 2737 * @param mode the basis of alignment: CLIP or OPTICAL 2738 * @return the alignment value 2739 */ 2740 abstract int getAlignmentValue(View view, int viewSize, int mode); 2741 2742 /** 2743 * Returns the size of the view specified by this alignment. 2744 * In the case of vertical alignments this method should return a height; for 2745 * horizontal alignments this method should return the width. 2746 * <p> 2747 * The default implementation returns {@code viewSize}. 2748 * 2749 * @param view the view to which this alignment should be applied 2750 * @param viewSize the measured size of the view 2751 * @param cellSize the size of the cell into which this view will be placed 2752 * @return the aligned size 2753 */ 2754 int getSizeInCell(View view, int viewSize, int cellSize) { 2755 return viewSize; 2756 } 2757 2758 Bounds getBounds() { 2759 return new Bounds(); 2760 } 2761 } 2762 2763 static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { 2764 @Override 2765 int getGravityOffset(View view, int cellDelta) { 2766 return UNDEFINED; 2767 } 2768 2769 @Override 2770 public int getAlignmentValue(View view, int viewSize, int mode) { 2771 return UNDEFINED; 2772 } 2773 }; 2774 2775 /** 2776 * Indicates that a view should be aligned with the <em>start</em> 2777 * edges of the other views in its cell group. 2778 */ 2779 private static final Alignment LEADING = new Alignment() { 2780 @Override 2781 int getGravityOffset(View view, int cellDelta) { 2782 return 0; 2783 } 2784 2785 @Override 2786 public int getAlignmentValue(View view, int viewSize, int mode) { 2787 return 0; 2788 } 2789 }; 2790 2791 /** 2792 * Indicates that a view should be aligned with the <em>end</em> 2793 * edges of the other views in its cell group. 2794 */ 2795 private static final Alignment TRAILING = new Alignment() { 2796 @Override 2797 int getGravityOffset(View view, int cellDelta) { 2798 return cellDelta; 2799 } 2800 2801 @Override 2802 public int getAlignmentValue(View view, int viewSize, int mode) { 2803 return viewSize; 2804 } 2805 }; 2806 2807 /** 2808 * Indicates that a view should be aligned with the <em>top</em> 2809 * edges of the other views in its cell group. 2810 */ 2811 public static final Alignment TOP = LEADING; 2812 2813 /** 2814 * Indicates that a view should be aligned with the <em>bottom</em> 2815 * edges of the other views in its cell group. 2816 */ 2817 public static final Alignment BOTTOM = TRAILING; 2818 2819 /** 2820 * Indicates that a view should be aligned with the <em>start</em> 2821 * edges of the other views in its cell group. 2822 */ 2823 public static final Alignment START = LEADING; 2824 2825 /** 2826 * Indicates that a view should be aligned with the <em>end</em> 2827 * edges of the other views in its cell group. 2828 */ 2829 public static final Alignment END = TRAILING; 2830 2831 private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) { 2832 return new Alignment() { 2833 @Override 2834 int getGravityOffset(View view, int cellDelta) { 2835 return (!view.isLayoutRtl() ? ltr : rtl).getGravityOffset(view, cellDelta); 2836 } 2837 2838 @Override 2839 public int getAlignmentValue(View view, int viewSize, int mode) { 2840 return (!view.isLayoutRtl() ? ltr : rtl).getAlignmentValue(view, viewSize, mode); 2841 } 2842 }; 2843 } 2844 2845 /** 2846 * Indicates that a view should be aligned with the <em>left</em> 2847 * edges of the other views in its cell group. 2848 */ 2849 public static final Alignment LEFT = createSwitchingAlignment(START, END); 2850 2851 /** 2852 * Indicates that a view should be aligned with the <em>right</em> 2853 * edges of the other views in its cell group. 2854 */ 2855 public static final Alignment RIGHT = createSwitchingAlignment(END, START); 2856 2857 /** 2858 * Indicates that a view should be <em>centered</em> with the other views in its cell group. 2859 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link 2860 * LayoutParams#columnSpec columnSpecs}. 2861 */ 2862 public static final Alignment CENTER = new Alignment() { 2863 @Override 2864 int getGravityOffset(View view, int cellDelta) { 2865 return cellDelta >> 1; 2866 } 2867 2868 @Override 2869 public int getAlignmentValue(View view, int viewSize, int mode) { 2870 return viewSize >> 1; 2871 } 2872 }; 2873 2874 /** 2875 * Indicates that a view should be aligned with the <em>baselines</em> 2876 * of the other views in its cell group. 2877 * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}. 2878 * 2879 * @see View#getBaseline() 2880 */ 2881 public static final Alignment BASELINE = new Alignment() { 2882 @Override 2883 int getGravityOffset(View view, int cellDelta) { 2884 return 0; // baseline gravity is top 2885 } 2886 2887 @Override 2888 public int getAlignmentValue(View view, int viewSize, int mode) { 2889 if (view.getVisibility() == GONE) { 2890 return 0; 2891 } 2892 int baseline = view.getBaseline(); 2893 return baseline == -1 ? UNDEFINED : baseline; 2894 } 2895 2896 @Override 2897 public Bounds getBounds() { 2898 return new Bounds() { 2899 /* 2900 In a baseline aligned row in which some components define a baseline 2901 and some don't, we need a third variable to properly account for all 2902 the sizes. This tracks the maximum size of all the components - 2903 including those that don't define a baseline. 2904 */ 2905 private int size; 2906 2907 @Override 2908 protected void reset() { 2909 super.reset(); 2910 size = Integer.MIN_VALUE; 2911 } 2912 2913 @Override 2914 protected void include(int before, int after) { 2915 super.include(before, after); 2916 size = max(size, before + after); 2917 } 2918 2919 @Override 2920 protected int size(boolean min) { 2921 return max(super.size(min), size); 2922 } 2923 2924 @Override 2925 protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) { 2926 return max(0, super.getOffset(gl, c, a, size, hrz)); 2927 } 2928 }; 2929 } 2930 }; 2931 2932 /** 2933 * Indicates that a view should expanded to fit the boundaries of its cell group. 2934 * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and 2935 * {@link LayoutParams#columnSpec columnSpecs}. 2936 */ 2937 public static final Alignment FILL = new Alignment() { 2938 @Override 2939 int getGravityOffset(View view, int cellDelta) { 2940 return 0; 2941 } 2942 2943 @Override 2944 public int getAlignmentValue(View view, int viewSize, int mode) { 2945 return UNDEFINED; 2946 } 2947 2948 @Override 2949 public int getSizeInCell(View view, int viewSize, int cellSize) { 2950 return cellSize; 2951 } 2952 }; 2953 2954 static boolean canStretch(int flexibility) { 2955 return (flexibility & CAN_STRETCH) != 0; 2956 } 2957 2958 private static final int INFLEXIBLE = 0; 2959 private static final int CAN_STRETCH = 2; 2960} 2961