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