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