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