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