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