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