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