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