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