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