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