1 /* 2 * Copyright (C) 2007 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 17 package android.widget; 18 19 import com.android.internal.R; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.util.AttributeSet; 24 import android.util.SparseBooleanArray; 25 import android.view.View; 26 import android.view.ViewGroup; 27 28 import java.util.regex.Pattern; 29 30 /** 31 * <p>A layout that arranges its children into rows and columns. 32 * A TableLayout consists of a number of {@link android.widget.TableRow} objects, 33 * each defining a row (actually, you can have other children, which will be 34 * explained below). TableLayout containers do not display border lines for 35 * their rows, columns, or cells. Each row has zero or more cells; each cell can 36 * hold one {@link android.view.View View} object. The table has as many columns 37 * as the row with the most cells. A table can leave cells empty. Cells can span 38 * columns, as they can in HTML.</p> 39 * 40 * <p>The width of a column is defined by the row with the widest cell in that 41 * column. However, a TableLayout can specify certain columns as shrinkable or 42 * stretchable by calling 43 * {@link #setColumnShrinkable(int, boolean) setColumnShrinkable()} 44 * or {@link #setColumnStretchable(int, boolean) setColumnStretchable()}. If 45 * marked as shrinkable, the column width can be shrunk to fit the table into 46 * its parent object. If marked as stretchable, it can expand in width to fit 47 * any extra space. The total width of the table is defined by its parent 48 * container. It is important to remember that a column can be both shrinkable 49 * and stretchable. In such a situation, the column will change its size to 50 * always use up the available space, but never more. Finally, you can hide a 51 * column by calling 52 * {@link #setColumnCollapsed(int,boolean) setColumnCollapsed()}.</p> 53 * 54 * <p>The children of a TableLayout cannot specify the <code>layout_width</code> 55 * attribute. Width is always <code>MATCH_PARENT</code>. However, the 56 * <code>layout_height</code> attribute can be defined by a child; default value 57 * is {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}. If the child 58 * is a {@link android.widget.TableRow}, then the height is always 59 * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p> 60 * 61 * <p> Cells must be added to a row in increasing column order, both in code and 62 * XML. Column numbers are zero-based. If you don't specify a column number for 63 * a child cell, it will autoincrement to the next available column. If you skip 64 * a column number, it will be considered an empty cell in that row. See the 65 * TableLayout examples in ApiDemos for examples of creating tables in XML.</p> 66 * 67 * <p>Although the typical child of a TableLayout is a TableRow, you can 68 * actually use any View subclass as a direct child of TableLayout. The View 69 * will be displayed as a single row that spans all the table columns.</p> 70 */ 71 public class TableLayout extends LinearLayout { 72 private int[] mMaxWidths; 73 private SparseBooleanArray mStretchableColumns; 74 private SparseBooleanArray mShrinkableColumns; 75 private SparseBooleanArray mCollapsedColumns; 76 77 private boolean mShrinkAllColumns; 78 private boolean mStretchAllColumns; 79 80 private TableLayout.PassThroughHierarchyChangeListener mPassThroughListener; 81 82 private boolean mInitialized; 83 84 /** 85 * <p>Creates a new TableLayout for the given context.</p> 86 * 87 * @param context the application environment 88 */ 89 public TableLayout(Context context) { 90 super(context); 91 initTableLayout(); 92 } 93 94 /** 95 * <p>Creates a new TableLayout for the given context and with the 96 * specified set attributes.</p> 97 * 98 * @param context the application environment 99 * @param attrs a collection of attributes 100 */ 101 public TableLayout(Context context, AttributeSet attrs) { 102 super(context, attrs); 103 104 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TableLayout); 105 106 String stretchedColumns = a.getString(R.styleable.TableLayout_stretchColumns); 107 if (stretchedColumns != null) { 108 if (stretchedColumns.charAt(0) == '*') { 109 mStretchAllColumns = true; 110 } else { 111 mStretchableColumns = parseColumns(stretchedColumns); 112 } 113 } 114 115 String shrinkedColumns = a.getString(R.styleable.TableLayout_shrinkColumns); 116 if (shrinkedColumns != null) { 117 if (shrinkedColumns.charAt(0) == '*') { 118 mShrinkAllColumns = true; 119 } else { 120 mShrinkableColumns = parseColumns(shrinkedColumns); 121 } 122 } 123 124 String collapsedColumns = a.getString(R.styleable.TableLayout_collapseColumns); 125 if (collapsedColumns != null) { 126 mCollapsedColumns = parseColumns(collapsedColumns); 127 } 128 129 a.recycle(); 130 initTableLayout(); 131 } 132 133 /** 134 * <p>Parses a sequence of columns ids defined in a CharSequence with the 135 * following pattern (regex): \d+(\s*,\s*\d+)*</p> 136 * 137 * <p>Examples: "1" or "13, 7, 6" or "".</p> 138 * 139 * <p>The result of the parsing is stored in a sparse boolean array. The 140 * parsed column ids are used as the keys of the sparse array. The values 141 * are always true.</p> 142 * 143 * @param sequence a sequence of column ids, can be empty but not null 144 * @return a sparse array of boolean mapping column indexes to the columns 145 * collapse state 146 */ 147 private static SparseBooleanArray parseColumns(String sequence) { 148 SparseBooleanArray columns = new SparseBooleanArray(); 149 Pattern pattern = Pattern.compile("\\s*,\\s*"); 150 String[] columnDefs = pattern.split(sequence); 151 152 for (String columnIdentifier : columnDefs) { 153 try { 154 int columnIndex = Integer.parseInt(columnIdentifier); 155 // only valid, i.e. positive, columns indexes are handled 156 if (columnIndex >= 0) { 157 // putting true in this sparse array indicates that the 158 // column index was defined in the XML file 159 columns.put(columnIndex, true); 160 } 161 } catch (NumberFormatException e) { 162 // we just ignore columns that don't exist 163 } 164 } 165 166 return columns; 167 } 168 169 /** 170 * <p>Performs initialization common to prorgrammatic use and XML use of 171 * this widget.</p> 172 */ 173 private void initTableLayout() { 174 if (mCollapsedColumns == null) { 175 mCollapsedColumns = new SparseBooleanArray(); 176 } 177 if (mStretchableColumns == null) { 178 mStretchableColumns = new SparseBooleanArray(); 179 } 180 if (mShrinkableColumns == null) { 181 mShrinkableColumns = new SparseBooleanArray(); 182 } 183 184 mPassThroughListener = new PassThroughHierarchyChangeListener(); 185 // make sure to call the parent class method to avoid potential 186 // infinite loops 187 super.setOnHierarchyChangeListener(mPassThroughListener); 188 189 mInitialized = true; 190 } 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override 196 public void setOnHierarchyChangeListener( 197 OnHierarchyChangeListener listener) { 198 // the user listener is delegated to our pass-through listener 199 mPassThroughListener.mOnHierarchyChangeListener = listener; 200 } 201 202 private void requestRowsLayout() { 203 if (mInitialized) { 204 final int count = getChildCount(); 205 for (int i = 0; i < count; i++) { 206 getChildAt(i).requestLayout(); 207 } 208 } 209 } 210 211 /** 212 * {@inheritDoc} 213 */ 214 @Override 215 public void requestLayout() { 216 if (mInitialized) { 217 int count = getChildCount(); 218 for (int i = 0; i < count; i++) { 219 getChildAt(i).forceLayout(); 220 } 221 } 222 223 super.requestLayout(); 224 } 225 226 /** 227 * <p>Indicates whether all columns are shrinkable or not.</p> 228 * 229 * @return true if all columns are shrinkable, false otherwise 230 */ 231 public boolean isShrinkAllColumns() { 232 return mShrinkAllColumns; 233 } 234 235 /** 236 * <p>Convenience method to mark all columns as shrinkable.</p> 237 * 238 * @param shrinkAllColumns true to mark all columns shrinkable 239 * 240 * @attr ref android.R.styleable#TableLayout_shrinkColumns 241 */ 242 public void setShrinkAllColumns(boolean shrinkAllColumns) { 243 mShrinkAllColumns = shrinkAllColumns; 244 } 245 246 /** 247 * <p>Indicates whether all columns are stretchable or not.</p> 248 * 249 * @return true if all columns are stretchable, false otherwise 250 */ 251 public boolean isStretchAllColumns() { 252 return mStretchAllColumns; 253 } 254 255 /** 256 * <p>Convenience method to mark all columns as stretchable.</p> 257 * 258 * @param stretchAllColumns true to mark all columns stretchable 259 * 260 * @attr ref android.R.styleable#TableLayout_stretchColumns 261 */ 262 public void setStretchAllColumns(boolean stretchAllColumns) { 263 mStretchAllColumns = stretchAllColumns; 264 } 265 266 /** 267 * <p>Collapses or restores a given column. When collapsed, a column 268 * does not appear on screen and the extra space is reclaimed by the 269 * other columns. A column is collapsed/restored only when it belongs to 270 * a {@link android.widget.TableRow}.</p> 271 * 272 * <p>Calling this method requests a layout operation.</p> 273 * 274 * @param columnIndex the index of the column 275 * @param isCollapsed true if the column must be collapsed, false otherwise 276 * 277 * @attr ref android.R.styleable#TableLayout_collapseColumns 278 */ 279 public void setColumnCollapsed(int columnIndex, boolean isCollapsed) { 280 // update the collapse status of the column 281 mCollapsedColumns.put(columnIndex, isCollapsed); 282 283 int count = getChildCount(); 284 for (int i = 0; i < count; i++) { 285 final View view = getChildAt(i); 286 if (view instanceof TableRow) { 287 ((TableRow) view).setColumnCollapsed(columnIndex, isCollapsed); 288 } 289 } 290 291 requestRowsLayout(); 292 } 293 294 /** 295 * <p>Returns the collapsed state of the specified column.</p> 296 * 297 * @param columnIndex the index of the column 298 * @return true if the column is collapsed, false otherwise 299 */ 300 public boolean isColumnCollapsed(int columnIndex) { 301 return mCollapsedColumns.get(columnIndex); 302 } 303 304 /** 305 * <p>Makes the given column stretchable or not. When stretchable, a column 306 * takes up as much as available space as possible in its row.</p> 307 * 308 * <p>Calling this method requests a layout operation.</p> 309 * 310 * @param columnIndex the index of the column 311 * @param isStretchable true if the column must be stretchable, 312 * false otherwise. Default is false. 313 * 314 * @attr ref android.R.styleable#TableLayout_stretchColumns 315 */ 316 public void setColumnStretchable(int columnIndex, boolean isStretchable) { 317 mStretchableColumns.put(columnIndex, isStretchable); 318 requestRowsLayout(); 319 } 320 321 /** 322 * <p>Returns whether the specified column is stretchable or not.</p> 323 * 324 * @param columnIndex the index of the column 325 * @return true if the column is stretchable, false otherwise 326 */ 327 public boolean isColumnStretchable(int columnIndex) { 328 return mStretchAllColumns || mStretchableColumns.get(columnIndex); 329 } 330 331 /** 332 * <p>Makes the given column shrinkable or not. When a row is too wide, the 333 * table can reclaim extra space from shrinkable columns.</p> 334 * 335 * <p>Calling this method requests a layout operation.</p> 336 * 337 * @param columnIndex the index of the column 338 * @param isShrinkable true if the column must be shrinkable, 339 * false otherwise. Default is false. 340 * 341 * @attr ref android.R.styleable#TableLayout_shrinkColumns 342 */ 343 public void setColumnShrinkable(int columnIndex, boolean isShrinkable) { 344 mShrinkableColumns.put(columnIndex, isShrinkable); 345 requestRowsLayout(); 346 } 347 348 /** 349 * <p>Returns whether the specified column is shrinkable or not.</p> 350 * 351 * @param columnIndex the index of the column 352 * @return true if the column is shrinkable, false otherwise. Default is false. 353 */ 354 public boolean isColumnShrinkable(int columnIndex) { 355 return mShrinkAllColumns || mShrinkableColumns.get(columnIndex); 356 } 357 358 /** 359 * <p>Applies the columns collapse status to a new row added to this 360 * table. This method is invoked by PassThroughHierarchyChangeListener 361 * upon child insertion.</p> 362 * 363 * <p>This method only applies to {@link android.widget.TableRow} 364 * instances.</p> 365 * 366 * @param child the newly added child 367 */ 368 private void trackCollapsedColumns(View child) { 369 if (child instanceof TableRow) { 370 final TableRow row = (TableRow) child; 371 final SparseBooleanArray collapsedColumns = mCollapsedColumns; 372 final int count = collapsedColumns.size(); 373 for (int i = 0; i < count; i++) { 374 int columnIndex = collapsedColumns.keyAt(i); 375 boolean isCollapsed = collapsedColumns.valueAt(i); 376 // the collapse status is set only when the column should be 377 // collapsed; otherwise, this might affect the default 378 // visibility of the row's children 379 if (isCollapsed) { 380 row.setColumnCollapsed(columnIndex, isCollapsed); 381 } 382 } 383 } 384 } 385 386 /** 387 * {@inheritDoc} 388 */ 389 @Override 390 public void addView(View child) { 391 super.addView(child); 392 requestRowsLayout(); 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override 399 public void addView(View child, int index) { 400 super.addView(child, index); 401 requestRowsLayout(); 402 } 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override 408 public void addView(View child, ViewGroup.LayoutParams params) { 409 super.addView(child, params); 410 requestRowsLayout(); 411 } 412 413 /** 414 * {@inheritDoc} 415 */ 416 @Override 417 public void addView(View child, int index, ViewGroup.LayoutParams params) { 418 super.addView(child, index, params); 419 requestRowsLayout(); 420 } 421 422 /** 423 * {@inheritDoc} 424 */ 425 @Override 426 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 427 // enforce vertical layout 428 measureVertical(widthMeasureSpec, heightMeasureSpec); 429 } 430 431 /** 432 * {@inheritDoc} 433 */ 434 @Override 435 protected void onLayout(boolean changed, int l, int t, int r, int b) { 436 // enforce vertical layout 437 layoutVertical(); 438 } 439 440 /** 441 * {@inheritDoc} 442 */ 443 @Override 444 void measureChildBeforeLayout(View child, int childIndex, 445 int widthMeasureSpec, int totalWidth, 446 int heightMeasureSpec, int totalHeight) { 447 // when the measured child is a table row, we force the width of its 448 // children with the widths computed in findLargestCells() 449 if (child instanceof TableRow) { 450 ((TableRow) child).setColumnsWidthConstraints(mMaxWidths); 451 } 452 453 super.measureChildBeforeLayout(child, childIndex, 454 widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); 455 } 456 457 /** 458 * {@inheritDoc} 459 */ 460 @Override 461 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { 462 findLargestCells(widthMeasureSpec); 463 shrinkAndStretchColumns(widthMeasureSpec); 464 465 super.measureVertical(widthMeasureSpec, heightMeasureSpec); 466 } 467 468 /** 469 * <p>Finds the largest cell in each column. For each column, the width of 470 * the largest cell is applied to all the other cells.</p> 471 * 472 * @param widthMeasureSpec the measure constraint imposed by our parent 473 */ 474 private void findLargestCells(int widthMeasureSpec) { 475 boolean firstRow = true; 476 477 // find the maximum width for each column 478 // the total number of columns is dynamically changed if we find 479 // wider rows as we go through the children 480 // the array is reused for each layout operation; the array can grow 481 // but never shrinks. Unused extra cells in the array are just ignored 482 // this behavior avoids to unnecessary grow the array after the first 483 // layout operation 484 final int count = getChildCount(); 485 for (int i = 0; i < count; i++) { 486 final View child = getChildAt(i); 487 if (child.getVisibility() == GONE) { 488 continue; 489 } 490 491 if (child instanceof TableRow) { 492 final TableRow row = (TableRow) child; 493 // forces the row's height 494 final ViewGroup.LayoutParams layoutParams = row.getLayoutParams(); 495 layoutParams.height = LayoutParams.WRAP_CONTENT; 496 497 final int[] widths = row.getColumnsWidths(widthMeasureSpec); 498 final int newLength = widths.length; 499 // this is the first row, we just need to copy the values 500 if (firstRow) { 501 if (mMaxWidths == null || mMaxWidths.length != newLength) { 502 mMaxWidths = new int[newLength]; 503 } 504 System.arraycopy(widths, 0, mMaxWidths, 0, newLength); 505 firstRow = false; 506 } else { 507 int length = mMaxWidths.length; 508 final int difference = newLength - length; 509 // the current row is wider than the previous rows, so 510 // we just grow the array and copy the values 511 if (difference > 0) { 512 final int[] oldMaxWidths = mMaxWidths; 513 mMaxWidths = new int[newLength]; 514 System.arraycopy(oldMaxWidths, 0, mMaxWidths, 0, 515 oldMaxWidths.length); 516 System.arraycopy(widths, oldMaxWidths.length, 517 mMaxWidths, oldMaxWidths.length, difference); 518 } 519 520 // the row is narrower or of the same width as the previous 521 // rows, so we find the maximum width for each column 522 // if the row is narrower than the previous ones, 523 // difference will be negative 524 final int[] maxWidths = mMaxWidths; 525 length = Math.min(length, newLength); 526 for (int j = 0; j < length; j++) { 527 maxWidths[j] = Math.max(maxWidths[j], widths[j]); 528 } 529 } 530 } 531 } 532 } 533 534 /** 535 * <p>Shrinks the columns if their total width is greater than the 536 * width allocated by widthMeasureSpec. When the total width is less 537 * than the allocated width, this method attempts to stretch columns 538 * to fill the remaining space.</p> 539 * 540 * @param widthMeasureSpec the width measure specification as indicated 541 * by this widget's parent 542 */ 543 private void shrinkAndStretchColumns(int widthMeasureSpec) { 544 // when we have no row, mMaxWidths is not initialized and the loop 545 // below could cause a NPE 546 if (mMaxWidths == null) { 547 return; 548 } 549 550 // should we honor AT_MOST, EXACTLY and UNSPECIFIED? 551 int totalWidth = 0; 552 for (int width : mMaxWidths) { 553 totalWidth += width; 554 } 555 556 int size = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight; 557 558 if ((totalWidth > size) && (mShrinkAllColumns || mShrinkableColumns.size() > 0)) { 559 // oops, the largest columns are wider than the row itself 560 // fairly redistribute the row's widh among the columns 561 mutateColumnsWidth(mShrinkableColumns, mShrinkAllColumns, size, totalWidth); 562 } else if ((totalWidth < size) && (mStretchAllColumns || mStretchableColumns.size() > 0)) { 563 // if we have some space left, we distribute it among the 564 // expandable columns 565 mutateColumnsWidth(mStretchableColumns, mStretchAllColumns, size, totalWidth); 566 } 567 } 568 569 private void mutateColumnsWidth(SparseBooleanArray columns, 570 boolean allColumns, int size, int totalWidth) { 571 int skipped = 0; 572 final int[] maxWidths = mMaxWidths; 573 final int length = maxWidths.length; 574 final int count = allColumns ? length : columns.size(); 575 final int totalExtraSpace = size - totalWidth; 576 int extraSpace = totalExtraSpace / count; 577 578 // Column's widths are changed: force child table rows to re-measure. 579 // (done by super.measureVertical after shrinkAndStretchColumns.) 580 final int nbChildren = getChildCount(); 581 for (int i = 0; i < nbChildren; i++) { 582 View child = getChildAt(i); 583 if (child instanceof TableRow) { 584 child.forceLayout(); 585 } 586 } 587 588 if (!allColumns) { 589 for (int i = 0; i < count; i++) { 590 int column = columns.keyAt(i); 591 if (columns.valueAt(i)) { 592 if (column < length) { 593 maxWidths[column] += extraSpace; 594 } else { 595 skipped++; 596 } 597 } 598 } 599 } else { 600 for (int i = 0; i < count; i++) { 601 maxWidths[i] += extraSpace; 602 } 603 604 // we don't skip any column so we can return right away 605 return; 606 } 607 608 if (skipped > 0 && skipped < count) { 609 // reclaim any extra space we left to columns that don't exist 610 extraSpace = skipped * extraSpace / (count - skipped); 611 for (int i = 0; i < count; i++) { 612 int column = columns.keyAt(i); 613 if (columns.valueAt(i) && column < length) { 614 if (extraSpace > maxWidths[column]) { 615 maxWidths[column] = 0; 616 } else { 617 maxWidths[column] += extraSpace; 618 } 619 } 620 } 621 } 622 } 623 624 /** 625 * {@inheritDoc} 626 */ 627 @Override 628 public LayoutParams generateLayoutParams(AttributeSet attrs) { 629 return new TableLayout.LayoutParams(getContext(), attrs); 630 } 631 632 /** 633 * Returns a set of layout parameters with a width of 634 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, 635 * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}. 636 */ 637 @Override 638 protected LinearLayout.LayoutParams generateDefaultLayoutParams() { 639 return new LayoutParams(); 640 } 641 642 /** 643 * {@inheritDoc} 644 */ 645 @Override 646 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 647 return p instanceof TableLayout.LayoutParams; 648 } 649 650 /** 651 * {@inheritDoc} 652 */ 653 @Override 654 protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 655 return new LayoutParams(p); 656 } 657 658 /** 659 * <p>This set of layout parameters enforces the width of each child to be 660 * {@link #MATCH_PARENT} and the height of each child to be 661 * {@link #WRAP_CONTENT}, but only if the height is not specified.</p> 662 */ 663 @SuppressWarnings({"UnusedDeclaration"}) 664 public static class LayoutParams extends LinearLayout.LayoutParams { 665 /** 666 * {@inheritDoc} 667 */ 668 public LayoutParams(Context c, AttributeSet attrs) { 669 super(c, attrs); 670 } 671 672 /** 673 * {@inheritDoc} 674 */ 675 public LayoutParams(int w, int h) { 676 super(MATCH_PARENT, h); 677 } 678 679 /** 680 * {@inheritDoc} 681 */ 682 public LayoutParams(int w, int h, float initWeight) { 683 super(MATCH_PARENT, h, initWeight); 684 } 685 686 /** 687 * <p>Sets the child width to 688 * {@link android.view.ViewGroup.LayoutParams} and the child height to 689 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p> 690 */ 691 public LayoutParams() { 692 super(MATCH_PARENT, WRAP_CONTENT); 693 } 694 695 /** 696 * {@inheritDoc} 697 */ 698 public LayoutParams(ViewGroup.LayoutParams p) { 699 super(p); 700 } 701 702 /** 703 * {@inheritDoc} 704 */ 705 public LayoutParams(MarginLayoutParams source) { 706 super(source); 707 } 708 709 /** 710 * <p>Fixes the row's width to 711 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}; the row's 712 * height is fixed to 713 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} if no layout 714 * height is specified.</p> 715 * 716 * @param a the styled attributes set 717 * @param widthAttr the width attribute to fetch 718 * @param heightAttr the height attribute to fetch 719 */ 720 @Override 721 protected void setBaseAttributes(TypedArray a, 722 int widthAttr, int heightAttr) { 723 this.width = MATCH_PARENT; 724 if (a.hasValue(heightAttr)) { 725 this.height = a.getLayoutDimension(heightAttr, "layout_height"); 726 } else { 727 this.height = WRAP_CONTENT; 728 } 729 } 730 } 731 732 /** 733 * <p>A pass-through listener acts upon the events and dispatches them 734 * to another listener. This allows the table layout to set its own internal 735 * hierarchy change listener without preventing the user to setup his.</p> 736 */ 737 private class PassThroughHierarchyChangeListener implements 738 OnHierarchyChangeListener { 739 private OnHierarchyChangeListener mOnHierarchyChangeListener; 740 741 /** 742 * {@inheritDoc} 743 */ 744 public void onChildViewAdded(View parent, View child) { 745 trackCollapsedColumns(child); 746 747 if (mOnHierarchyChangeListener != null) { 748 mOnHierarchyChangeListener.onChildViewAdded(parent, child); 749 } 750 } 751 752 /** 753 * {@inheritDoc} 754 */ 755 public void onChildViewRemoved(View parent, View child) { 756 if (mOnHierarchyChangeListener != null) { 757 mOnHierarchyChangeListener.onChildViewRemoved(parent, child); 758 } 759 } 760 } 761 } 762