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