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