GridLayout.java revision c655ba5e467090eb4f839f148ac31b50c389ffb2
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 hAlignment = columnGroup.alignment; 847 Alignment vAlignment = 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 c2ax = protect(hAlignment.getAlignmentValue(null, cellWidth - colBounds.size())); 856 int c2ay = protect(vAlignment.getAlignmentValue(null, cellHeight - rowBounds.size())); 857 858 if (mMarginsIncludedInAlignment) { 859 int leftMargin = getMargin(view, true, true); 860 int topMargin = getMargin(view, true, false); 861 int rightMargin = getMargin(view, false, true); 862 int bottomMargin = getMargin(view, false, false); 863 864 // Same calculation as getMeasurementIncludingMargin() 865 int measuredWidth = leftMargin + pWidth + rightMargin; 866 int measuredHeight = topMargin + pHeight + bottomMargin; 867 868 // Alignment offsets: the location of the view relative to its alignment group. 869 int a2vx = colBounds.before - hAlignment.getAlignmentValue(view, measuredWidth); 870 int a2vy = rowBounds.before - vAlignment.getAlignmentValue(view, measuredHeight); 871 872 dx = c2ax + a2vx + leftMargin; 873 dy = c2ay + a2vy + topMargin; 874 875 cellWidth -= leftMargin + rightMargin; 876 cellHeight -= topMargin + bottomMargin; 877 } else { 878 // Alignment offsets: the location of the view relative to its alignment group. 879 int a2vx = colBounds.before - hAlignment.getAlignmentValue(view, pWidth); 880 int a2vy = rowBounds.before - vAlignment.getAlignmentValue(view, pHeight); 881 882 dx = c2ax + a2vx; 883 dy = c2ay + a2vy; 884 } 885 886 int width = hAlignment.getSizeInCell(view, pWidth, cellWidth); 887 int height = vAlignment.getSizeInCell(view, pHeight, cellHeight); 888 889 int cx = paddingLeft + x1 + dx; 890 int cy = paddingTop + y1 + dy; 891 view.layout(cx, cy, cx + width, cy + height); 892 } 893 } 894 895 // Inner classes 896 897 /* 898 This internal class houses the algorithm for computing the locations of grid lines; 899 along either the horizontal or vertical axis. A GridLayout uses two instances of this class - 900 distinguished by the "horizontal" flag which is true for the horizontal axis and false 901 for the vertical one. 902 */ 903 private class Axis { 904 private static final int MIN_VALUE = -1000000; 905 906 private static final int UNVISITED = 0; 907 private static final int PENDING = 1; 908 private static final int COMPLETE = 2; 909 910 public final boolean horizontal; 911 912 public int count = UNDEFINED; 913 public boolean countValid = false; 914 public boolean countWasExplicitySet = false; 915 916 PackedMap<Group, Bounds> groupBounds; 917 public boolean groupBoundsValid = false; 918 919 PackedMap<Interval, MutableInt> spanSizes; 920 public boolean spanSizesValid = false; 921 922 public int[] leadingMargins; 923 public boolean leadingMarginsValid = false; 924 925 public int[] trailingMargins; 926 public boolean trailingMarginsValid = false; 927 928 public Arc[] arcs; 929 public boolean arcsValid = false; 930 931 public int[] minima; 932 public boolean minimaValid = false; 933 934 public float[] weights; 935 public int[] locations; 936 937 private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED; 938 939 private Axis(boolean horizontal) { 940 this.horizontal = horizontal; 941 } 942 943 private int maxIndex() { 944 // note the number Integer.MIN_VALUE + 1 comes up in undefined cells 945 int count = -1; 946 for (int i = 0, size = getChildCount(); i < size; i++) { 947 LayoutParams params = getLayoutParams(getChildAt(i)); 948 Group g = horizontal ? params.columnGroup : params.rowGroup; 949 count = max(count, g.span.min); 950 count = max(count, g.span.max); 951 } 952 return count == -1 ? UNDEFINED : count; 953 } 954 955 public int getCount() { 956 if (!countValid) { 957 count = max(0, maxIndex()); // if there are no cells, the count is zero 958 countValid = true; 959 } 960 return count; 961 } 962 963 public void setCount(int count) { 964 this.count = count; 965 this.countWasExplicitySet = count != UNDEFINED; 966 } 967 968 public boolean isOrderPreserved() { 969 return mOrderPreserved; 970 } 971 972 public void setOrderPreserved(boolean orderPreserved) { 973 mOrderPreserved = orderPreserved; 974 invalidateStructure(); 975 } 976 977 private PackedMap<Group, Bounds> createGroupBounds() { 978 int N = getChildCount(); 979 Group[] groups = new Group[N]; 980 Bounds[] bounds = new Bounds[N]; 981 for (int i = 0; i < N; i++) { 982 LayoutParams lp = getLayoutParams(getChildAt(i)); 983 Group group = horizontal ? lp.columnGroup : lp.rowGroup; 984 985 groups[i] = group; 986 bounds[i] = new Bounds(); 987 } 988 989 return new PackedMap<Group, Bounds>(groups, bounds); 990 } 991 992 private void computeGroupBounds() { 993 for (int i = 0; i < groupBounds.values.length; i++) { 994 groupBounds.values[i].reset(); 995 } 996 for (int i = 0, N = getChildCount(); i < N; i++) { 997 View c = getChildAt(i); 998 LayoutParams lp = getLayoutParams(c); 999 Group g = horizontal ? lp.columnGroup : lp.rowGroup; 1000 1001 Bounds bounds = groupBounds.getValue(i); 1002 1003 int size = getMeasurementIncludingMargin(c, horizontal, PRF); 1004 // todo test this works correctly when the returned value is UNDEFINED 1005 int before = g.alignment.getAlignmentValue(c, size); 1006 bounds.include(before, size - before); 1007 } 1008 } 1009 1010 private PackedMap<Group, Bounds> getGroupBounds() { 1011 if (groupBounds == null) { 1012 groupBounds = createGroupBounds(); 1013 } 1014 if (!groupBoundsValid) { 1015 computeGroupBounds(); 1016 groupBoundsValid = true; 1017 } 1018 return groupBounds; 1019 } 1020 1021 // Add values computed by alignment - taking the max of all alignments in each span 1022 private PackedMap<Interval, MutableInt> createSpanSizes() { 1023 PackedMap<Group, Bounds> groupBounds = getGroupBounds(); 1024 int N = groupBounds.keys.length; 1025 Interval[] spans = new Interval[N]; 1026 MutableInt[] values = new MutableInt[N]; 1027 for (int i = 0; i < N; i++) { 1028 Interval key = groupBounds.keys[i].span; 1029 1030 spans[i] = key; 1031 values[i] = new MutableInt(); 1032 } 1033 return new PackedMap<Interval, MutableInt>(spans, values); 1034 } 1035 1036 private void computeSpanSizes() { 1037 MutableInt[] spans = spanSizes.values; 1038 for (int i = 0; i < spans.length; i++) { 1039 spans[i].reset(); 1040 } 1041 1042 Bounds[] bounds = getGroupBounds().values; // use getter to trigger a re-evaluation 1043 for (int i = 0; i < bounds.length; i++) { 1044 int value = bounds[i].size(); 1045 1046 MutableInt valueHolder = spanSizes.getValue(i); 1047 valueHolder.value = max(valueHolder.value, value); 1048 } 1049 } 1050 1051 private PackedMap<Interval, MutableInt> getSpanSizes() { 1052 if (spanSizes == null) { 1053 spanSizes = createSpanSizes(); 1054 } 1055 if (!spanSizesValid) { 1056 computeSpanSizes(); 1057 spanSizesValid = true; 1058 } 1059 return spanSizes; 1060 } 1061 1062 private void include(List<Arc> arcs, Interval key, MutableInt size) { 1063 // this bit below should really be computed outside here - 1064 // its just to stop default (col>0) constraints obliterating valid entries 1065 for (Arc arc : arcs) { 1066 Interval span = arc.span; 1067 if (span.equals(key)) { 1068 return; 1069 } 1070 } 1071 arcs.add(new Arc(key, size)); 1072 } 1073 1074 private void include2(List<Arc> arcs, Interval span, MutableInt min, MutableInt max, 1075 boolean both) { 1076 include(arcs, span, min); 1077 if (both) { 1078 // todo 1079// include(arcs, span.inverse(), max.neg()); 1080 } 1081 } 1082 1083 private void include2(List<Arc> arcs, Interval span, int min, int max, boolean both) { 1084 include2(arcs, span, new MutableInt(min), new MutableInt(max), both); 1085 } 1086 1087 // Group arcs by their first vertex, returning an array of arrays. 1088 // This is linear in the number of arcs. 1089 private Arc[][] groupArcsByFirstVertex(Arc[] arcs) { 1090 int N = getCount() + 1;// the number of vertices 1091 Arc[][] result = new Arc[N][]; 1092 int[] sizes = new int[N]; 1093 for (Arc arc : arcs) { 1094 sizes[arc.span.min]++; 1095 } 1096 for (int i = 0; i < sizes.length; i++) { 1097 result[i] = new Arc[sizes[i]]; 1098 } 1099 // reuse the sizes array to hold the current last elements as we insert each arc 1100 Arrays.fill(sizes, 0); 1101 for (Arc arc : arcs) { 1102 int i = arc.span.min; 1103 result[i][sizes[i]++] = arc; 1104 } 1105 1106 return result; 1107 } 1108 1109 /* 1110 Topological sort. 1111 */ 1112 private Arc[] topologicalSort(final Arc[] arcs, int start) { 1113 // todo ensure the <start> vertex is added in edge cases 1114 final List<Arc> result = new ArrayList<Arc>(); 1115 new Object() { 1116 Arc[][] arcsByFirstVertex = groupArcsByFirstVertex(arcs); 1117 int[] visited = new int[getCount() + 1]; 1118 1119 boolean completesCycle(int loc) { 1120 int state = visited[loc]; 1121 if (state == UNVISITED) { 1122 visited[loc] = PENDING; 1123 for (Arc arc : arcsByFirstVertex[loc]) { 1124 Interval span = arc.span; 1125 // the recursive call 1126 if (completesCycle(span.max)) { 1127 // which arcs get set here is dependent on the order 1128 // in which we explore nodes 1129 arc.completesCycle = true; 1130 } 1131 result.add(arc); 1132 } 1133 visited[loc] = COMPLETE; 1134 } else if (state == PENDING) { 1135 return true; 1136 } else if (state == COMPLETE) { 1137 } 1138 return false; 1139 } 1140 }.completesCycle(start); 1141 Collections.reverse(result); 1142 assert arcs.length == result.size(); 1143 return result.toArray(new Arc[result.size()]); 1144 } 1145 1146 private boolean[] findUsed(Collection<Arc> arcs) { 1147 boolean[] result = new boolean[getCount()]; 1148 for (Arc arc : arcs) { 1149 Interval span = arc.span; 1150 int min = min(span.min, span.max); 1151 int max = max(span.min, span.max); 1152 for (int i = min; i < max; i++) { 1153 result[i] = true; 1154 } 1155 } 1156 return result; 1157 } 1158 1159 // todo unify with findUsed above. Both routines analyze which rows/columns are empty. 1160 private Collection<Interval> getSpacers() { 1161 List<Interval> result = new ArrayList<Interval>(); 1162 int N = getCount() + 1; 1163 int[] leadingEdgeCount = new int[N]; 1164 int[] trailingEdgeCount = new int[N]; 1165 for (int i = 0, size = getChildCount(); i < size; i++) { 1166 LayoutParams lp = getLayoutParams(getChildAt(i)); 1167 Group g = horizontal ? lp.columnGroup : lp.rowGroup; 1168 Interval span = g.span; 1169 leadingEdgeCount[span.min]++; 1170 trailingEdgeCount[span.max]++; 1171 } 1172 1173 int lastTrailingEdge = 0; 1174 1175 // treat the parent's edges like peer edges of the opposite type 1176 trailingEdgeCount[0] = 1; 1177 leadingEdgeCount[N - 1] = 1; 1178 1179 for (int i = 0; i < N; i++) { 1180 if (trailingEdgeCount[i] > 0) { 1181 lastTrailingEdge = i; 1182 continue; // if this is also a leading edge, don't add a space of length zero 1183 } 1184 if (leadingEdgeCount[i] > 0) { 1185 result.add(new Interval(lastTrailingEdge, i)); 1186 } 1187 } 1188 return result; 1189 } 1190 1191 private Arc[] createArcs() { 1192 List<Arc> spanToSize = new ArrayList<Arc>(); 1193 1194 // Add all the preferred elements that were not defined by the user. 1195 PackedMap<Interval, MutableInt> spanSizes = getSpanSizes(); 1196 for (int i = 0; i < spanSizes.keys.length; i++) { 1197 Interval key = spanSizes.keys[i]; 1198 MutableInt value = spanSizes.values[i]; 1199 // todo remove value duplicate 1200 include2(spanToSize, key, value, value, accommodateBothMinAndMax); 1201 } 1202 1203 // Find redundant rows/cols and glue them together with 0-length arcs to link the tree 1204 boolean[] used = findUsed(spanToSize); 1205 for (int i = 0; i < getCount(); i++) { 1206 if (!used[i]) { 1207 Interval span = new Interval(i, i + 1); 1208 include(spanToSize, span, new MutableInt(0)); 1209 include(spanToSize, span.inverse(), new MutableInt(0)); 1210 } 1211 } 1212 1213 if (mOrderPreserved) { 1214 // Add preferred gaps 1215 for (int i = 0; i < getCount(); i++) { 1216 if (used[i]) { 1217 include2(spanToSize, new Interval(i, i + 1), 0, 0, false); 1218 } 1219 } 1220 } else { 1221 for (Interval gap : getSpacers()) { 1222 include2(spanToSize, gap, 0, 0, false); 1223 } 1224 } 1225 Arc[] arcs = spanToSize.toArray(new Arc[spanToSize.size()]); 1226 return topologicalSort(arcs, 0); 1227 } 1228 1229 public Arc[] getArcs() { 1230 if (arcs == null) { 1231 arcs = createArcs(); 1232 } 1233 if (!arcsValid) { 1234 getSpanSizes(); 1235 arcsValid = true; 1236 } 1237 return arcs; 1238 } 1239 1240 private boolean relax(int[] locations, Arc entry) { 1241 Interval span = entry.span; 1242 int u = span.min; 1243 int v = span.max; 1244 int value = entry.value.value; 1245 int candidate = locations[u] + value; 1246 if (candidate > locations[v]) { 1247 locations[v] = candidate; 1248 return true; 1249 } 1250 return false; 1251 } 1252 1253 /* 1254 Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N) 1255 1256 GridLayout converts its requirements into a system of linear constraints of the 1257 form: 1258 1259 x[i] - x[j] < a[k] 1260 1261 Where the x[i] are variables and the a[k] are constants. 1262 1263 For example, if the variables were instead labeled x, y, z we might have: 1264 1265 x - y < 17 1266 y - z < 23 1267 z - x < 42 1268 1269 This is a special case of the Linear Programming problem that is, in turn, 1270 equivalent to the single-source shortest paths problem on a digraph, for 1271 which the O(n^2) Bellman-Ford algorithm the most commonly used general solution. 1272 1273 Other algorithms are faster in the case where no arcs have negative weights 1274 but allowing negative weights turns out to be the same as accommodating maximum 1275 size requirements as well as minimum ones. 1276 1277 Bellman-Ford works by iteratively 'relaxing' constraints over all nodes (an O(N) 1278 process) and performing this step N times. Proof of correctness hinges on the 1279 fact that there can be no negative weight chains of length > N - unless a 1280 'negative weight loop' exists. The algorithm catches this case in a final 1281 checking phase that reports failure. 1282 1283 By topologically sorting the nodes and checking this condition at each step 1284 typical layout problems complete after the first iteration and the algorithm 1285 completes in O(N) steps with very low constants. 1286 */ 1287 private int[] solve(Arc[] arcs, int[] locations) { 1288 int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. 1289 1290 boolean changed = false; 1291 // We take one extra pass over traditional Bellman-Ford (and omit their final step) 1292 for (int i = 0; i < N; i++) { 1293 changed = false; 1294 for (int j = 0, length = arcs.length; j < length; j++) { 1295 changed = changed | relax(locations, arcs[j]); 1296 } 1297 if (!changed) { 1298 if (DEBUG) { 1299 Log.d(TAG, "Iteration " + 1300 " completed after " + (1 + i) + " steps out of " + N); 1301 } 1302 break; 1303 } 1304 } 1305 if (changed) { 1306 Log.d(TAG, "*** Algorithm failed to terminate ***"); 1307 } 1308 return locations; 1309 } 1310 1311 private void computeMargins(boolean leading) { 1312 int[] margins = leading ? leadingMargins : trailingMargins; 1313 for (int i = 0, size = getChildCount(); i < size; i++) { 1314 View c = getChildAt(i); 1315 LayoutParams lp = getLayoutParams(c); 1316 Group g = horizontal ? lp.columnGroup : lp.rowGroup; 1317 Interval span = g.span; 1318 int index = leading ? span.min : span.max; 1319 margins[index] = max(margins[index], getMargin(c, leading, horizontal)); 1320 } 1321 } 1322 1323 private int[] getLeadingMargins() { 1324 if (leadingMargins == null) { 1325 leadingMargins = new int[getCount() + 1]; 1326 } 1327 if (!leadingMarginsValid) { 1328 computeMargins(true); 1329 leadingMarginsValid = true; 1330 } 1331 return leadingMargins; 1332 } 1333 1334 private int[] getTrailingMargins() { 1335 if (trailingMargins == null) { 1336 trailingMargins = new int[getCount() + 1]; 1337 } 1338 if (!trailingMarginsValid) { 1339 computeMargins(false); 1340 trailingMarginsValid = true; 1341 } 1342 return trailingMargins; 1343 } 1344 1345 private void addMargins() { 1346 int[] leadingMargins = getLeadingMargins(); 1347 int[] trailingMargins = getTrailingMargins(); 1348 1349 int delta = 0; 1350 for (int i = 0, N = getCount(); i < N; i++) { 1351 int margins = leadingMargins[i] + trailingMargins[i + 1]; 1352 delta += margins; 1353 minima[i + 1] += delta; 1354 } 1355 } 1356 1357 private int getLocationIncludingMargin(View view, boolean leading, int index) { 1358 int location = locations[index]; 1359 int margin; 1360 if (!mMarginsIncludedInAlignment) { 1361 margin = (leading ? leadingMargins : trailingMargins)[index]; 1362 } else { 1363 margin = 0; 1364 } 1365 return leading ? (location + margin) : (location - margin); 1366 } 1367 1368 private void computeMinima(int[] a) { 1369 Arrays.fill(a, MIN_VALUE); 1370 a[0] = 0; 1371 solve(getArcs(), a); 1372 if (!mMarginsIncludedInAlignment) { 1373 addMargins(); 1374 } 1375 } 1376 1377 private int[] getMinima() { 1378 if (minima == null) { 1379 int N = getCount() + 1; 1380 minima = new int[N]; 1381 } 1382 if (!minimaValid) { 1383 computeMinima(minima); 1384 minimaValid = true; 1385 } 1386 return minima; 1387 } 1388 1389 private void computeWeights() { 1390 for (int i = 0, N = getChildCount(); i < N; i++) { 1391 LayoutParams lp = getLayoutParams(getChildAt(i)); 1392 Group g = horizontal ? lp.columnGroup : lp.rowGroup; 1393 Interval span = g.span; 1394 int penultimateIndex = span.max - 1; 1395 weights[penultimateIndex] += horizontal ? lp.columnWeight : lp.rowWeight; 1396 } 1397 } 1398 1399 private float[] getWeights() { 1400 if (weights == null) { 1401 int N = getCount(); 1402 weights = new float[N]; 1403 } 1404 computeWeights(); 1405 return weights; 1406 } 1407 1408 private int[] getLocations() { 1409 if (locations == null) { 1410 int N = getCount() + 1; 1411 locations = new int[N]; 1412 } 1413 return locations; 1414 } 1415 1416 // External entry points 1417 1418 private int size(int[] locations) { 1419 return max2(locations, 0) - locations[0]; 1420 } 1421 1422 private int getMin() { 1423 return size(getMinima()); 1424 } 1425 1426 private void layout(int targetSize) { 1427 int[] mins = getMinima(); 1428 1429 int totalDelta = max(0, targetSize - size(mins)); // confine to expansion 1430 1431 float[] weights = getWeights(); 1432 float totalWeight = sum(weights); 1433 1434 if (totalWeight == 0f && weights.length > 0) { 1435 weights[weights.length - 1] = 1; 1436 totalWeight = 1; 1437 } 1438 1439 int[] locations = getLocations(); 1440 int cumulativeDelta = 0; 1441 1442 // note |weights| = |locations| - 1 1443 for (int i = 0; i < weights.length; i++) { 1444 float weight = weights[i]; 1445 int delta = (int) (totalDelta * weight / totalWeight); 1446 cumulativeDelta += delta; 1447 locations[i + 1] = mins[i + 1] + cumulativeDelta; 1448 1449 totalDelta -= delta; 1450 totalWeight -= weight; 1451 } 1452 } 1453 1454 private void invalidateStructure() { 1455 countValid = false; 1456 1457 groupBounds = null; 1458 spanSizes = null; 1459 leadingMargins = null; 1460 trailingMargins = null; 1461 minima = null; 1462 weights = null; 1463 locations = null; 1464 1465 invalidateValues(); 1466 } 1467 1468 private void invalidateValues() { 1469 groupBoundsValid = false; 1470 spanSizesValid = false; 1471 arcsValid = false; 1472 leadingMarginsValid = false; 1473 trailingMarginsValid = false; 1474 minimaValid = false; 1475 } 1476 } 1477 1478 /** 1479 * Layout information associated with each of the children of a GridLayout. 1480 * <p> 1481 * GridLayout supports both row and column spanning and arbitrary forms of alignment within 1482 * each cell group. The fundamental parameters associated with each cell group are 1483 * gathered into their vertical and horizontal components and stored 1484 * in the {@link #rowGroup} and {@link #columnGroup} layout parameters. 1485 * {@link Group Groups} are immutable structures and may be shared between the layout 1486 * parameters of different children. 1487 * <p> 1488 * The row and column groups contain the leading and trailing indices along each axis 1489 * and together specify the four grid indices that delimit the cells of this cell group. 1490 * <p> 1491 * The {@link Group#alignment alignment} fields of the row and column groups together specify 1492 * both aspects of alignment within the cell group. It is also possible to specify a child's 1493 * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} 1494 * method. 1495 * <p> 1496 * See {@link GridLayout} for a description of the conventions used by GridLayout 1497 * in reference to grid indices. 1498 * 1499 * <h4>Default values</h4> 1500 * 1501 * <ul> 1502 * <li>{@link #width} = {@link #WRAP_CONTENT}</li> 1503 * <li>{@link #height} = {@link #WRAP_CONTENT}</li> 1504 * <li>{@link #topMargin} = 0 when 1505 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1506 * {@code false}; otherwise {@link #UNDEFINED}, to 1507 * indicate that a default value should be computed on demand. </li> 1508 * <li>{@link #leftMargin} = 0 when 1509 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1510 * {@code false}; otherwise {@link #UNDEFINED}, to 1511 * indicate that a default value should be computed on demand. </li> 1512 * <li>{@link #bottomMargin} = 0 when 1513 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1514 * {@code false}; otherwise {@link #UNDEFINED}, to 1515 * indicate that a default value should be computed on demand. </li> 1516 * <li>{@link #rightMargin} = 0 when 1517 * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1518 * {@code false}; otherwise {@link #UNDEFINED}, to 1519 * indicate that a default value should be computed on demand. </li> 1520 * <li>{@link #rowGroup}{@code .span} = {@code [0, 1]} </li> 1521 * <li>{@link #rowGroup}{@code .alignment} = {@link #BASELINE} </li> 1522 * <li>{@link #columnGroup}{@code .span} = {@code [0, 1]} </li> 1523 * <li>{@link #columnGroup}{@code .alignment} = {@link #LEFT} </li> 1524 * <li>{@link #rowWeight} = {@code 0f} </li> 1525 * <li>{@link #columnWeight} = {@code 0f} </li> 1526 * </ul> 1527 * 1528 * @attr ref android.R.styleable#GridLayout_Layout_layout_row 1529 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan 1530 * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight 1531 * @attr ref android.R.styleable#GridLayout_Layout_layout_column 1532 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan 1533 * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight 1534 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1535 */ 1536 public static class LayoutParams extends MarginLayoutParams { 1537 1538 // Default values 1539 1540 private static final int DEFAULT_WIDTH = WRAP_CONTENT; 1541 private static final int DEFAULT_HEIGHT = WRAP_CONTENT; 1542 private static final int DEFAULT_MARGIN = UNDEFINED; 1543 private static final int DEFAULT_ROW = UNDEFINED; 1544 private static final int DEFAULT_COLUMN = UNDEFINED; 1545 private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); 1546 private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); 1547 private static final Alignment DEFAULT_COLUMN_ALIGNMENT = LEFT; 1548 private static final Alignment DEFAULT_ROW_ALIGNMENT = BASELINE; 1549 private static final Group DEFAULT_COLUMN_GROUP = 1550 new Group(DEFAULT_SPAN, DEFAULT_COLUMN_ALIGNMENT); 1551 private static final Group DEFAULT_ROW_GROUP = 1552 new Group(DEFAULT_SPAN, DEFAULT_ROW_ALIGNMENT); 1553 private static final int DEFAULT_WEIGHT_0 = 0; 1554 private static final int DEFAULT_WEIGHT_1 = 1; 1555 1556 // Misc 1557 1558 private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2); 1559 private static final Alignment[] COLUMN_ALIGNMENTS = { LEFT, CENTER, RIGHT }; 1560 private static final Alignment[] ROW_ALIGNMENTS = { TOP, CENTER, BOTTOM }; 1561 1562 // TypedArray indices 1563 1564 private static final int MARGIN = styleable.ViewGroup_MarginLayout_layout_margin; 1565 private static final int LEFT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginLeft; 1566 private static final int TOP_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginTop; 1567 private static final int RIGHT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginRight; 1568 private static final int BOTTOM_MARGIN = 1569 styleable.ViewGroup_MarginLayout_layout_marginBottom; 1570 1571 private static final int COLUMN = styleable.GridLayout_Layout_layout_column; 1572 private static final int COLUMN_SPAN = styleable.GridLayout_Layout_layout_columnSpan; 1573 private static final int COLUMN_WEIGHT = styleable.GridLayout_Layout_layout_columnWeight; 1574 private static final int ROW = styleable.GridLayout_Layout_layout_row; 1575 private static final int ROW_SPAN = styleable.GridLayout_Layout_layout_rowSpan; 1576 private static final int ROW_WEIGHT = styleable.GridLayout_Layout_layout_rowWeight; 1577 private static final int GRAVITY = styleable.GridLayout_Layout_layout_gravity; 1578 1579 // Instance variables 1580 1581 /** 1582 * The group that specifies the vertical characteristics of the cell group 1583 * described by these layout parameters. 1584 */ 1585 public Group rowGroup; 1586 /** 1587 * The group that specifies the horizontal characteristics of the cell group 1588 * described by these layout parameters. 1589 */ 1590 public Group columnGroup; 1591 /** 1592 * The proportional space that should be taken by the associated row group 1593 * during excess space distribution. 1594 */ 1595 public float rowWeight; 1596 /** 1597 * The proportional space that should be taken by the associated column group 1598 * during excess space distribution. 1599 */ 1600 public float columnWeight; 1601 1602 // Constructors 1603 1604 private LayoutParams( 1605 int width, int height, 1606 int left, int top, int right, int bottom, 1607 Group rowGroup, Group columnGroup, float rowWeight, float columnWeight) { 1608 super(width, height); 1609 setMargins(left, top, right, bottom); 1610 this.rowGroup = rowGroup; 1611 this.columnGroup = columnGroup; 1612 this.rowWeight = rowWeight; 1613 this.columnWeight = columnWeight; 1614 } 1615 1616 /** 1617 * Constructs a new LayoutParams instance for this <code>rowGroup</code> 1618 * and <code>columnGroup</code>. All other fields are initialized with 1619 * default values as defined in {@link LayoutParams}. 1620 * 1621 * @param rowGroup the rowGroup 1622 * @param columnGroup the columnGroup 1623 */ 1624 public LayoutParams(Group rowGroup, Group columnGroup) { 1625 this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 1626 DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 1627 rowGroup, columnGroup, DEFAULT_WEIGHT_0, DEFAULT_WEIGHT_0); 1628 } 1629 1630 /** 1631 * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 1632 */ 1633 public LayoutParams() { 1634 this(DEFAULT_ROW_GROUP, DEFAULT_COLUMN_GROUP); 1635 } 1636 1637 // Copying constructors 1638 1639 /** 1640 * {@inheritDoc} 1641 */ 1642 public LayoutParams(ViewGroup.LayoutParams params) { 1643 super(params); 1644 } 1645 1646 /** 1647 * {@inheritDoc} 1648 */ 1649 public LayoutParams(MarginLayoutParams params) { 1650 super(params); 1651 } 1652 1653 /** 1654 * {@inheritDoc} 1655 */ 1656 public LayoutParams(LayoutParams that) { 1657 super(that); 1658 this.columnGroup = that.columnGroup; 1659 this.rowGroup = that.rowGroup; 1660 this.columnWeight = that.columnWeight; 1661 this.rowWeight = that.rowWeight; 1662 } 1663 1664 // AttributeSet constructors 1665 1666 private LayoutParams(Context context, AttributeSet attrs, int defaultGravity) { 1667 super(context, attrs); 1668 reInitSuper(context, attrs); 1669 init(context, attrs, defaultGravity); 1670 } 1671 1672 /** 1673 * {@inheritDoc} 1674 * 1675 * Values not defined in the attribute set take the default values 1676 * defined in {@link LayoutParams}. 1677 */ 1678 public LayoutParams(Context context, AttributeSet attrs) { 1679 this(context, attrs, Gravity.NO_GRAVITY); 1680 } 1681 1682 // Implementation 1683 1684 private static boolean definesVertical(int gravity) { 1685 return gravity > 0 && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != 0; 1686 } 1687 1688 private static boolean definesHorizontal(int gravity) { 1689 return gravity > 0 && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 0; 1690 } 1691 1692 private static <T> T getAlignment(T[] alignments, T fill, int min, int max, 1693 boolean isUndefined, T defaultValue) { 1694 if (isUndefined) { 1695 return defaultValue; 1696 } 1697 return min != max ? fill : alignments[min]; 1698 } 1699 1700 // Reinitialise the margins using a different default policy than MarginLayoutParams. 1701 // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 1702 // so that a layout manager default can be accessed post set up. We need this as, at the 1703 // point of installation, we do not know how many rows/cols there are and therefore 1704 // which elements are positioned next to the container's trailing edges. We need to 1705 // know this as margins around the container's boundary should have different 1706 // defaults to those between peers. 1707 1708 // This method could be parametrized and moved into MarginLayout. 1709 private void reInitSuper(Context context, AttributeSet attrs) { 1710 TypedArray a = context.obtainStyledAttributes(attrs, styleable.ViewGroup_MarginLayout); 1711 try { 1712 int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 1713 1714 this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 1715 this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 1716 this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 1717 this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 1718 } finally { 1719 a.recycle(); 1720 } 1721 } 1722 1723 // Gravity. For conversion from the static the integers defined in the Gravity class, 1724 // use Gravity.apply() to apply gravity to a view of zero size and see where it ends up. 1725 private static Alignment getColumnAlignment(int gravity, int width) { 1726 Rect r = new Rect(0, 0, 0, 0); 1727 Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r); 1728 1729 boolean fill = (width == MATCH_PARENT); 1730 Alignment defaultAlignment = fill ? FILL : DEFAULT_COLUMN_ALIGNMENT; 1731 return getAlignment(COLUMN_ALIGNMENTS, FILL, r.left, r.right, 1732 !definesHorizontal(gravity), defaultAlignment); 1733 } 1734 1735 private static Alignment getRowAlignment(int gravity, int height) { 1736 Rect r = new Rect(0, 0, 0, 0); 1737 Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r); 1738 1739 boolean fill = (height == MATCH_PARENT); 1740 Alignment defaultAlignment = fill ? FILL : DEFAULT_ROW_ALIGNMENT; 1741 return getAlignment(ROW_ALIGNMENTS, FILL, r.top, r.bottom, 1742 !definesVertical(gravity), defaultAlignment); 1743 } 1744 1745 private int getDefaultWeight(int size) { 1746 return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; 1747 } 1748 1749 private void init(Context context, AttributeSet attrs, int defaultGravity) { 1750 TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout_Layout); 1751 try { 1752 int gravity = a.getInteger(GRAVITY, defaultGravity); 1753 1754 int column = a.getInteger(COLUMN, DEFAULT_COLUMN); 1755 int columnSpan = a.getInteger(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 1756 Interval hSpan = new Interval(column, column + columnSpan); 1757 this.columnGroup = new Group(hSpan, getColumnAlignment(gravity, width)); 1758 this.columnWeight = a.getFloat(COLUMN_WEIGHT, getDefaultWeight(width)); 1759 1760 int row = a.getInteger(ROW, DEFAULT_ROW); 1761 int rowSpan = a.getInteger(ROW_SPAN, DEFAULT_SPAN_SIZE); 1762 Interval vSpan = new Interval(row, row + rowSpan); 1763 this.rowGroup = new Group(vSpan, getRowAlignment(gravity, height)); 1764 this.rowWeight = a.getFloat(ROW_WEIGHT, getDefaultWeight(height)); 1765 } finally { 1766 a.recycle(); 1767 } 1768 } 1769 1770 /** 1771 * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 1772 * See {@link android.view.Gravity}. 1773 * 1774 * @param gravity the new gravity value 1775 * 1776 * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1777 */ 1778 public void setGravity(int gravity) { 1779 columnGroup = columnGroup.copyWriteAlignment(getColumnAlignment(gravity, width)); 1780 rowGroup = rowGroup.copyWriteAlignment(getRowAlignment(gravity, height)); 1781 } 1782 1783 @Override 1784 protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 1785 this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 1786 this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 1787 } 1788 1789 private void setRowGroupSpan(Interval span) { 1790 rowGroup = rowGroup.copyWriteSpan(span); 1791 } 1792 1793 private void setColumnGroupSpan(Interval span) { 1794 columnGroup = columnGroup.copyWriteSpan(span); 1795 } 1796 } 1797 1798 /* 1799 In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 1800 Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 1801 */ 1802 private static class Arc { 1803 public final Interval span; 1804 public final MutableInt value; 1805 public boolean completesCycle; 1806 1807 public Arc(Interval span, MutableInt value) { 1808 this.span = span; 1809 this.value = value; 1810 } 1811 1812 @Override 1813 public String toString() { 1814 return span + " " + (completesCycle ? "+>" : "->") + " " + value; 1815 } 1816 } 1817 1818 // A mutable Integer - used to avoid heap allocation during the layout operation 1819 1820 private static class MutableInt { 1821 public int value; 1822 1823 private MutableInt() { 1824 reset(); 1825 } 1826 1827 private MutableInt(int value) { 1828 this.value = value; 1829 } 1830 1831 private void reset() { 1832 value = Integer.MIN_VALUE; 1833 } 1834 } 1835 1836 /* 1837 This data structure is used in place of a Map where we have an index that refers to the order 1838 in which each key/value pairs were added to the map. In this case we store keys and values 1839 in arrays of a length that is equal to the number of unique keys. We also maintain an 1840 array of indexes from insertion order to the compacted arrays of keys and values. 1841 1842 Note that behavior differs from that of a LinkedHashMap in that repeated entries 1843 *do* get added multiples times. So the length of index is equals to the number of 1844 items added. 1845 1846 This is useful in the GridLayout class where we can rely on the order of children not 1847 changing during layout - to use integer-based lookup for our internal structures 1848 rather than using (and storing) an implementation of Map<Key, ?>. 1849 */ 1850 @SuppressWarnings(value = "unchecked") 1851 private static class PackedMap<K, V> { 1852 public final int[] index; 1853 public final K[] keys; 1854 public final V[] values; 1855 1856 private PackedMap(K[] keys, V[] values) { 1857 this.index = createIndex(keys); 1858 1859 this.keys = compact(keys, index); 1860 this.values = compact(values, index); 1861 } 1862 1863 private K getKey(int i) { 1864 return keys[index[i]]; 1865 } 1866 1867 private V getValue(int i) { 1868 return values[index[i]]; 1869 } 1870 1871 private static <K> int[] createIndex(K[] keys) { 1872 int size = keys.length; 1873 int[] result = new int[size]; 1874 1875 Map<K, Integer> keyToIndex = new HashMap<K, Integer>(); 1876 for (int i = 0; i < size; i++) { 1877 K key = keys[i]; 1878 Integer index = keyToIndex.get(key); 1879 if (index == null) { 1880 index = keyToIndex.size(); 1881 keyToIndex.put(key, index); 1882 } 1883 result[i] = index; 1884 } 1885 return result; 1886 } 1887 1888 /* 1889 Create a compact array of keys or values using the supplied index. 1890 */ 1891 private static <K> K[] compact(K[] a, int[] index) { 1892 int size = a.length; 1893 Class<?> componentType = a.getClass().getComponentType(); 1894 K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 1895 1896 // this overwrite duplicates, retaining the last equivalent entry 1897 for (int i = 0; i < size; i++) { 1898 result[index[i]] = a[i]; 1899 } 1900 return result; 1901 } 1902 } 1903 1904 /* 1905 For each Group (with a given alignment) we need to store the amount of space required 1906 before the alignment point and the amount of space required after it. One side of this 1907 calculation is always 0 for LEADING and TRAILING alignments but we don't make use of this. 1908 For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 1909 simple optimisations are possible. 1910 1911 The general algorithm therefore is to create a Map (actually a PackedMap) from 1912 Group to Bounds and to loop through all Views in the group taking the maximum 1913 of the values for each View. 1914 */ 1915 private static class Bounds { 1916 public int before; 1917 public int after; 1918 1919 private Bounds() { 1920 reset(); 1921 } 1922 1923 private void reset() { 1924 before = Integer.MIN_VALUE; 1925 after = Integer.MIN_VALUE; 1926 } 1927 1928 private void include(int before, int after) { 1929 this.before = max(this.before, before); 1930 this.after = max(this.after, after); 1931 } 1932 1933 private int size() { 1934 return before + after; 1935 } 1936 1937 @Override 1938 public String toString() { 1939 return "Bounds{" + 1940 "before=" + before + 1941 ", after=" + after + 1942 '}'; 1943 } 1944 } 1945 1946 /** 1947 * An Interval represents a contiguous range of values that lie between 1948 * the interval's {@link #min} and {@link #max} values. 1949 * <p> 1950 * Intervals are immutable so may be passed as values and used as keys in hash tables. 1951 * It is not necessary to have multiple instances of Intervals which have the same 1952 * {@link #min} and {@link #max} values. 1953 * <p> 1954 * Intervals are often written as {@code [min, max]} and represent the set of values 1955 * {@code x} such that {@code min <= x < max}. 1956 */ 1957 /* package */ static class Interval { 1958 /** 1959 * The minimum value. 1960 */ 1961 public final int min; 1962 1963 /** 1964 * The maximum value. 1965 */ 1966 public final int max; 1967 1968 /** 1969 * Construct a new Interval, {@code interval}, where: 1970 * <ul> 1971 * <li> {@code interval.min = min} </li> 1972 * <li> {@code interval.max = max} </li> 1973 * </ul> 1974 * 1975 * @param min the minimum value. 1976 * @param max the maximum value. 1977 */ 1978 public Interval(int min, int max) { 1979 this.min = min; 1980 this.max = max; 1981 } 1982 1983 private int size() { 1984 return max - min; 1985 } 1986 1987 private Interval inverse() { 1988 return new Interval(max, min); 1989 } 1990 1991 /** 1992 * Returns {@code true} if the {@link #getClass class}, 1993 * {@link #min} and {@link #max} properties of this Interval and the 1994 * supplied parameter are pairwise equal; {@code false} otherwise. 1995 * 1996 * @param that the object to compare this interval with 1997 * 1998 * @return {@code true} if the specified object is equal to this 1999 * {@code Interval}, {@code false} otherwise. 2000 */ 2001 @Override 2002 public boolean equals(Object that) { 2003 if (this == that) { 2004 return true; 2005 } 2006 if (that == null || getClass() != that.getClass()) { 2007 return false; 2008 } 2009 2010 Interval interval = (Interval) that; 2011 2012 if (max != interval.max) { 2013 return false; 2014 } 2015 if (min != interval.min) { 2016 return false; 2017 } 2018 2019 return true; 2020 } 2021 2022 @Override 2023 public int hashCode() { 2024 int result = min; 2025 result = 31 * result + max; 2026 return result; 2027 } 2028 2029 @Override 2030 public String toString() { 2031 return "[" + min + ", " + max + "]"; 2032 } 2033 } 2034 2035 /** 2036 * A group specifies either the horizontal or vertical characteristics of a group of 2037 * cells. 2038 * <p> 2039 * Groups are immutable and so may be shared between views with the same 2040 * {@code span} and {@code alignment}. 2041 */ 2042 public static class Group { 2043 /** 2044 * The grid indices of the leading and trailing edges of this cell group for the 2045 * appropriate axis. 2046 * <p> 2047 * See {@link GridLayout} for a description of the conventions used by GridLayout 2048 * for grid indices. 2049 */ 2050 /* package */ final Interval span; 2051 /** 2052 * Specifies how cells should be aligned in this group. 2053 * For row groups, this specifies the vertical alignment. 2054 * For column groups, this specifies the horizontal alignment. 2055 */ 2056 public final Alignment alignment; 2057 2058 /** 2059 * Construct a new Group, {@code group}, where: 2060 * <ul> 2061 * <li> {@code group.span = span} </li> 2062 * <li> {@code group.alignment = alignment} </li> 2063 * </ul> 2064 * 2065 * @param span the span 2066 * @param alignment the alignment 2067 */ 2068 /* package */ Group(Interval span, Alignment alignment) { 2069 this.span = span; 2070 this.alignment = alignment; 2071 } 2072 2073 /** 2074 * Construct a new Group, {@code group}, where: 2075 * <ul> 2076 * <li> {@code group.span = [start, start + size]} </li> 2077 * <li> {@code group.alignment = alignment} </li> 2078 * </ul> 2079 * 2080 * @param start the start 2081 * @param size the size 2082 * @param alignment the alignment 2083 */ 2084 public Group(int start, int size, Alignment alignment) { 2085 this(new Interval(start, start + size), alignment); 2086 } 2087 2088 /** 2089 * Construct a new Group, {@code group}, where: 2090 * <ul> 2091 * <li> {@code group.span = [start, start + 1]} </li> 2092 * <li> {@code group.alignment = alignment} </li> 2093 * </ul> 2094 * 2095 * @param start the start index 2096 * @param alignment the alignment 2097 */ 2098 public Group(int start, Alignment alignment) { 2099 this(start, 1, alignment); 2100 } 2101 2102 private Group copyWriteSpan(Interval span) { 2103 return new Group(span, alignment); 2104 } 2105 2106 private Group copyWriteAlignment(Alignment alignment) { 2107 return new Group(span, alignment); 2108 } 2109 2110 /** 2111 * Returns {@code true} if the {@link #getClass class}, {@link #alignment} and {@code span} 2112 * properties of this Group and the supplied parameter are pairwise equal, 2113 * {@code false} otherwise. 2114 * 2115 * @param that the object to compare this group with 2116 * 2117 * @return {@code true} if the specified object is equal to this 2118 * {@code Group}; {@code false} otherwise 2119 */ 2120 @Override 2121 public boolean equals(Object that) { 2122 if (this == that) { 2123 return true; 2124 } 2125 if (that == null || getClass() != that.getClass()) { 2126 return false; 2127 } 2128 2129 Group group = (Group) that; 2130 2131 if (!alignment.equals(group.alignment)) { 2132 return false; 2133 } 2134 if (!span.equals(group.span)) { 2135 return false; 2136 } 2137 2138 return true; 2139 } 2140 2141 @Override 2142 public int hashCode() { 2143 int result = span.hashCode(); 2144 result = 31 * result + alignment.hashCode(); 2145 return result; 2146 } 2147 } 2148 2149 /** 2150 * Alignments specify where a view should be placed within a cell group and 2151 * what size it should be. 2152 * <p> 2153 * The {@link LayoutParams} class contains a {@link LayoutParams#rowGroup rowGroup} 2154 * and a {@link LayoutParams#columnGroup columnGroup} each of which contains an 2155 * {@link Group#alignment alignment}. Overall placement of the view in the cell 2156 * group is specified by the two alignments which act along each axis independently. 2157 * <p> 2158 * An Alignment implementation must define the {@link #getAlignmentValue(View, int)} 2159 * to return the appropriate value for the type of alignment being defined. 2160 * The enclosing algorithms position the children 2161 * so that the values returned from the alignment 2162 * are the same for all of the views in a group. 2163 * <p> 2164 * The GridLayout class defines the most common alignments used in general layout: 2165 * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link 2166 * #BASELINE} and {@link #FILL}. 2167 */ 2168 public static interface Alignment { 2169 /** 2170 * Returns an alignment value. In the case of vertical alignments the value 2171 * returned should indicate the distance from the top of the view to the 2172 * alignment location. 2173 * For horizontal alignments measurement is made from the left edge of the component. 2174 * 2175 * @param view the view to which this alignment should be applied 2176 * @param viewSize the measured size of the view 2177 * @return the alignment value 2178 */ 2179 public int getAlignmentValue(View view, int viewSize); 2180 2181 /** 2182 * Returns the size of the view specified by this alignment. 2183 * In the case of vertical alignments this method should return a height; for 2184 * horizontal alignments this method should return the width. 2185 * 2186 * @param view the view to which this alignment should be applied 2187 * @param viewSize the measured size of the view 2188 * @param cellSize the size of the cell into which this view will be placed 2189 * @return the aligned size 2190 */ 2191 public int getSizeInCell(View view, int viewSize, int cellSize); 2192 } 2193 2194 private static abstract class AbstractAlignment implements Alignment { 2195 public int getSizeInCell(View view, int viewSize, int cellSize) { 2196 return viewSize; 2197 } 2198 } 2199 2200 private static final Alignment LEADING = new AbstractAlignment() { 2201 public int getAlignmentValue(View view, int viewSize) { 2202 return 0; 2203 } 2204 2205 }; 2206 2207 private static final Alignment TRAILING = new AbstractAlignment() { 2208 public int getAlignmentValue(View view, int viewSize) { 2209 return viewSize; 2210 } 2211 }; 2212 2213 /** 2214 * Indicates that a view should be aligned with the <em>top</em> 2215 * edges of the other views in its cell group. 2216 */ 2217 public static final Alignment TOP = LEADING; 2218 2219 /** 2220 * Indicates that a view should be aligned with the <em>bottom</em> 2221 * edges of the other views in its cell group. 2222 */ 2223 public static final Alignment BOTTOM = TRAILING; 2224 2225 /** 2226 * Indicates that a view should be aligned with the <em>right</em> 2227 * edges of the other views in its cell group. 2228 */ 2229 public static final Alignment RIGHT = TRAILING; 2230 2231 /** 2232 * Indicates that a view should be aligned with the <em>left</em> 2233 * edges of the other views in its cell group. 2234 */ 2235 public static final Alignment LEFT = LEADING; 2236 2237 /** 2238 * Indicates that a view should be <em>centered</em> with the other views in its cell group. 2239 * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and {@link 2240 * LayoutParams#columnGroup columnGroups}. 2241 */ 2242 public static final Alignment CENTER = new AbstractAlignment() { 2243 public int getAlignmentValue(View view, int viewSize) { 2244 return viewSize >> 1; 2245 } 2246 }; 2247 2248 /** 2249 * Indicates that a view should be aligned with the <em>baselines</em> 2250 * of the other views in its cell group. 2251 * This constant may only be used as an alignment in {@link LayoutParams#rowGroup rowGroups}. 2252 * 2253 * @see View#getBaseline() 2254 */ 2255 public static final Alignment BASELINE = new AbstractAlignment() { 2256 public int getAlignmentValue(View view, int height) { 2257 if (view == null) { 2258 return UNDEFINED; 2259 } 2260 int baseline = view.getBaseline(); 2261 if (baseline == -1) { 2262 return UNDEFINED; 2263 } else { 2264 return baseline; 2265 } 2266 } 2267 2268 }; 2269 2270 /** 2271 * Indicates that a view should expanded to fit the boundaries of its cell group. 2272 * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and 2273 * {@link LayoutParams#columnGroup columnGroups}. 2274 */ 2275 public static final Alignment FILL = new Alignment() { 2276 public int getAlignmentValue(View view, int viewSize) { 2277 return UNDEFINED; 2278 } 2279 2280 public int getSizeInCell(View view, int viewSize, int cellSize) { 2281 return cellSize; 2282 } 2283 }; 2284}