TableLayout.java revision c8d5f7d607123966651b2bc521ba9c4470952b34
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
17package android.widget;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.util.AttributeSet;
24import android.util.SparseBooleanArray;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.accessibility.AccessibilityEvent;
28import android.view.accessibility.AccessibilityNodeInfo;
29
30import java.util.regex.Pattern;
31
32/**
33 * <p>A layout that arranges its children into rows and columns.
34 * A TableLayout consists of a number of {@link android.widget.TableRow} objects,
35 * each defining a row (actually, you can have other children, which will be
36 * explained below). TableLayout containers do not display border lines for
37 * their rows, columns, or cells. Each row has zero or more cells; each cell can
38 * hold one {@link android.view.View View} object. The table has as many columns
39 * as the row with the most cells. A table can leave cells empty. Cells can span
40 * columns, as they can in HTML.</p>
41 *
42 * <p>The width of a column is defined by the row with the widest cell in that
43 * column. However, a TableLayout can specify certain columns as shrinkable or
44 * stretchable by calling
45 * {@link #setColumnShrinkable(int, boolean) setColumnShrinkable()}
46 * or {@link #setColumnStretchable(int, boolean) setColumnStretchable()}. If
47 * marked as shrinkable, the column width can be shrunk to fit the table into
48 * its parent object. If marked as stretchable, it can expand in width to fit
49 * any extra space. The total width of the table is defined by its parent
50 * container. It is important to remember that a column can be both shrinkable
51 * and stretchable. In such a situation, the column will change its size to
52 * always use up the available space, but never more. Finally, you can hide a
53 * column by calling
54 * {@link #setColumnCollapsed(int,boolean) setColumnCollapsed()}.</p>
55 *
56 * <p>The children of a TableLayout cannot specify the <code>layout_width</code>
57 * attribute. Width is always <code>MATCH_PARENT</code>. However, the
58 * <code>layout_height</code> attribute can be defined by a child; default value
59 * is {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}. If the child
60 * is a {@link android.widget.TableRow}, then the height is always
61 * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
62 *
63 * <p> Cells must be added to a row in increasing column order, both in code and
64 * XML. Column numbers are zero-based. If you don't specify a column number for
65 * a child cell, it will autoincrement to the next available column. If you skip
66 * a column number, it will be considered an empty cell in that row. See the
67 * TableLayout examples in ApiDemos for examples of creating tables in XML.</p>
68 *
69 * <p>Although the typical child of a TableLayout is a TableRow, you can
70 * actually use any View subclass as a direct child of TableLayout. The View
71 * will be displayed as a single row that spans all the table columns.</p>
72 *
73 */
74public 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     * @attr ref android.R.styleable#TableLayout_shrinkColumns
235     */
236    public boolean isShrinkAllColumns() {
237        return mShrinkAllColumns;
238    }
239
240    /**
241     * <p>Convenience method to mark all columns as shrinkable.</p>
242     *
243     * @param shrinkAllColumns true to mark all columns shrinkable
244     *
245     * @attr ref android.R.styleable#TableLayout_shrinkColumns
246     */
247    public void setShrinkAllColumns(boolean shrinkAllColumns) {
248        mShrinkAllColumns = shrinkAllColumns;
249    }
250
251    /**
252     * <p>Indicates whether all columns are stretchable or not.</p>
253     *
254     * @return true if all columns are stretchable, false otherwise
255     *
256     * @attr ref android.R.styleable#TableLayout_stretchColumns
257     */
258    public boolean isStretchAllColumns() {
259        return mStretchAllColumns;
260    }
261
262    /**
263     * <p>Convenience method to mark all columns as stretchable.</p>
264     *
265     * @param stretchAllColumns true to mark all columns stretchable
266     *
267     * @attr ref android.R.styleable#TableLayout_stretchColumns
268     */
269    public void setStretchAllColumns(boolean stretchAllColumns) {
270        mStretchAllColumns = stretchAllColumns;
271    }
272
273    /**
274     * <p>Collapses or restores a given column. When collapsed, a column
275     * does not appear on screen and the extra space is reclaimed by the
276     * other columns. A column is collapsed/restored only when it belongs to
277     * a {@link android.widget.TableRow}.</p>
278     *
279     * <p>Calling this method requests a layout operation.</p>
280     *
281     * @param columnIndex the index of the column
282     * @param isCollapsed true if the column must be collapsed, false otherwise
283     *
284     * @attr ref android.R.styleable#TableLayout_collapseColumns
285     */
286    public void setColumnCollapsed(int columnIndex, boolean isCollapsed) {
287        // update the collapse status of the column
288        mCollapsedColumns.put(columnIndex, isCollapsed);
289
290        int count = getChildCount();
291        for (int i = 0; i < count; i++) {
292            final View view = getChildAt(i);
293            if (view instanceof TableRow) {
294                ((TableRow) view).setColumnCollapsed(columnIndex, isCollapsed);
295            }
296        }
297
298        requestRowsLayout();
299    }
300
301    /**
302     * <p>Returns the collapsed state of the specified column.</p>
303     *
304     * @param columnIndex the index of the column
305     * @return true if the column is collapsed, false otherwise
306     */
307    public boolean isColumnCollapsed(int columnIndex) {
308        return mCollapsedColumns.get(columnIndex);
309    }
310
311    /**
312     * <p>Makes the given column stretchable or not. When stretchable, a column
313     * takes up as much as available space as possible in its row.</p>
314     *
315     * <p>Calling this method requests a layout operation.</p>
316     *
317     * @param columnIndex the index of the column
318     * @param isStretchable true if the column must be stretchable,
319     *                      false otherwise. Default is false.
320     *
321     * @attr ref android.R.styleable#TableLayout_stretchColumns
322     */
323    public void setColumnStretchable(int columnIndex, boolean isStretchable) {
324        mStretchableColumns.put(columnIndex, isStretchable);
325        requestRowsLayout();
326    }
327
328    /**
329     * <p>Returns whether the specified column is stretchable or not.</p>
330     *
331     * @param columnIndex the index of the column
332     * @return true if the column is stretchable, false otherwise
333     */
334    public boolean isColumnStretchable(int columnIndex) {
335        return mStretchAllColumns || mStretchableColumns.get(columnIndex);
336    }
337
338    /**
339     * <p>Makes the given column shrinkable or not. When a row is too wide, the
340     * table can reclaim extra space from shrinkable columns.</p>
341     *
342     * <p>Calling this method requests a layout operation.</p>
343     *
344     * @param columnIndex the index of the column
345     * @param isShrinkable true if the column must be shrinkable,
346     *                     false otherwise. Default is false.
347     *
348     * @attr ref android.R.styleable#TableLayout_shrinkColumns
349     */
350    public void setColumnShrinkable(int columnIndex, boolean isShrinkable) {
351        mShrinkableColumns.put(columnIndex, isShrinkable);
352        requestRowsLayout();
353    }
354
355    /**
356     * <p>Returns whether the specified column is shrinkable or not.</p>
357     *
358     * @param columnIndex the index of the column
359     * @return true if the column is shrinkable, false otherwise. Default is false.
360     */
361    public boolean isColumnShrinkable(int columnIndex) {
362        return mShrinkAllColumns || mShrinkableColumns.get(columnIndex);
363    }
364
365    /**
366     * <p>Applies the columns collapse status to a new row added to this
367     * table. This method is invoked by PassThroughHierarchyChangeListener
368     * upon child insertion.</p>
369     *
370     * <p>This method only applies to {@link android.widget.TableRow}
371     * instances.</p>
372     *
373     * @param child the newly added child
374     */
375    private void trackCollapsedColumns(View child) {
376        if (child instanceof TableRow) {
377            final TableRow row = (TableRow) child;
378            final SparseBooleanArray collapsedColumns = mCollapsedColumns;
379            final int count = collapsedColumns.size();
380            for (int i = 0; i < count; i++) {
381                int columnIndex = collapsedColumns.keyAt(i);
382                boolean isCollapsed = collapsedColumns.valueAt(i);
383                // the collapse status is set only when the column should be
384                // collapsed; otherwise, this might affect the default
385                // visibility of the row's children
386                if (isCollapsed) {
387                    row.setColumnCollapsed(columnIndex, isCollapsed);
388                }
389            }
390        }
391    }
392
393    /**
394     * {@inheritDoc}
395     */
396    @Override
397    public void addView(View child) {
398        super.addView(child);
399        requestRowsLayout();
400    }
401
402    /**
403     * {@inheritDoc}
404     */
405    @Override
406    public void addView(View child, int index) {
407        super.addView(child, index);
408        requestRowsLayout();
409    }
410
411    /**
412     * {@inheritDoc}
413     */
414    @Override
415    public void addView(View child, ViewGroup.LayoutParams params) {
416        super.addView(child, params);
417        requestRowsLayout();
418    }
419
420    /**
421     * {@inheritDoc}
422     */
423    @Override
424    public void addView(View child, int index, ViewGroup.LayoutParams params) {
425        super.addView(child, index, params);
426        requestRowsLayout();
427    }
428
429    /**
430     * {@inheritDoc}
431     */
432    @Override
433    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
434        // enforce vertical layout
435        measureVertical(widthMeasureSpec, heightMeasureSpec);
436    }
437
438    /**
439     * {@inheritDoc}
440     */
441    @Override
442    protected void onLayout(boolean changed, int l, int t, int r, int b) {
443        // enforce vertical layout
444        layoutVertical();
445    }
446
447    /**
448     * {@inheritDoc}
449     */
450    @Override
451    void measureChildBeforeLayout(View child, int childIndex,
452            int widthMeasureSpec, int totalWidth,
453            int heightMeasureSpec, int totalHeight) {
454        // when the measured child is a table row, we force the width of its
455        // children with the widths computed in findLargestCells()
456        if (child instanceof TableRow) {
457            ((TableRow) child).setColumnsWidthConstraints(mMaxWidths);
458        }
459
460        super.measureChildBeforeLayout(child, childIndex,
461                widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
462    }
463
464    /**
465     * {@inheritDoc}
466     */
467    @Override
468    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
469        findLargestCells(widthMeasureSpec);
470        shrinkAndStretchColumns(widthMeasureSpec);
471
472        super.measureVertical(widthMeasureSpec, heightMeasureSpec);
473    }
474
475    /**
476     * <p>Finds the largest cell in each column. For each column, the width of
477     * the largest cell is applied to all the other cells.</p>
478     *
479     * @param widthMeasureSpec the measure constraint imposed by our parent
480     */
481    private void findLargestCells(int widthMeasureSpec) {
482        boolean firstRow = true;
483
484        // find the maximum width for each column
485        // the total number of columns is dynamically changed if we find
486        // wider rows as we go through the children
487        // the array is reused for each layout operation; the array can grow
488        // but never shrinks. Unused extra cells in the array are just ignored
489        // this behavior avoids to unnecessary grow the array after the first
490        // layout operation
491        final int count = getChildCount();
492        for (int i = 0; i < count; i++) {
493            final View child = getChildAt(i);
494            if (child.getVisibility() == GONE) {
495                continue;
496            }
497
498            if (child instanceof TableRow) {
499                final TableRow row = (TableRow) child;
500                // forces the row's height
501                final ViewGroup.LayoutParams layoutParams = row.getLayoutParams();
502                layoutParams.height = LayoutParams.WRAP_CONTENT;
503
504                final int[] widths = row.getColumnsWidths(widthMeasureSpec);
505                final int newLength = widths.length;
506                // this is the first row, we just need to copy the values
507                if (firstRow) {
508                    if (mMaxWidths == null || mMaxWidths.length != newLength) {
509                        mMaxWidths = new int[newLength];
510                    }
511                    System.arraycopy(widths, 0, mMaxWidths, 0, newLength);
512                    firstRow = false;
513                } else {
514                    int length = mMaxWidths.length;
515                    final int difference = newLength - length;
516                    // the current row is wider than the previous rows, so
517                    // we just grow the array and copy the values
518                    if (difference > 0) {
519                        final int[] oldMaxWidths = mMaxWidths;
520                        mMaxWidths = new int[newLength];
521                        System.arraycopy(oldMaxWidths, 0, mMaxWidths, 0,
522                                oldMaxWidths.length);
523                        System.arraycopy(widths, oldMaxWidths.length,
524                                mMaxWidths, oldMaxWidths.length, difference);
525                    }
526
527                    // the row is narrower or of the same width as the previous
528                    // rows, so we find the maximum width for each column
529                    // if the row is narrower than the previous ones,
530                    // difference will be negative
531                    final int[] maxWidths = mMaxWidths;
532                    length = Math.min(length, newLength);
533                    for (int j = 0; j < length; j++) {
534                        maxWidths[j] = Math.max(maxWidths[j], widths[j]);
535                    }
536                }
537            }
538        }
539    }
540
541    /**
542     * <p>Shrinks the columns if their total width is greater than the
543     * width allocated by widthMeasureSpec. When the total width is less
544     * than the allocated width, this method attempts to stretch columns
545     * to fill the remaining space.</p>
546     *
547     * @param widthMeasureSpec the width measure specification as indicated
548     *                         by this widget's parent
549     */
550    private void shrinkAndStretchColumns(int widthMeasureSpec) {
551        // when we have no row, mMaxWidths is not initialized and the loop
552        // below could cause a NPE
553        if (mMaxWidths == null) {
554            return;
555        }
556
557        // should we honor AT_MOST, EXACTLY and UNSPECIFIED?
558        int totalWidth = 0;
559        for (int width : mMaxWidths) {
560            totalWidth += width;
561        }
562
563        int size = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
564
565        if ((totalWidth > size) && (mShrinkAllColumns || mShrinkableColumns.size() > 0)) {
566            // oops, the largest columns are wider than the row itself
567            // fairly redistribute the row's width among the columns
568            mutateColumnsWidth(mShrinkableColumns, mShrinkAllColumns, size, totalWidth);
569        } else if ((totalWidth < size) && (mStretchAllColumns || mStretchableColumns.size() > 0)) {
570            // if we have some space left, we distribute it among the
571            // expandable columns
572            mutateColumnsWidth(mStretchableColumns, mStretchAllColumns, size, totalWidth);
573        }
574    }
575
576    private void mutateColumnsWidth(SparseBooleanArray columns,
577            boolean allColumns, int size, int totalWidth) {
578        int skipped = 0;
579        final int[] maxWidths = mMaxWidths;
580        final int length = maxWidths.length;
581        final int count = allColumns ? length : columns.size();
582        final int totalExtraSpace = size - totalWidth;
583        int extraSpace = totalExtraSpace / count;
584
585        // Column's widths are changed: force child table rows to re-measure.
586        // (done by super.measureVertical after shrinkAndStretchColumns.)
587        final int nbChildren = getChildCount();
588        for (int i = 0; i < nbChildren; i++) {
589            View child = getChildAt(i);
590            if (child instanceof TableRow) {
591                child.forceLayout();
592            }
593        }
594
595        if (!allColumns) {
596            for (int i = 0; i < count; i++) {
597                int column = columns.keyAt(i);
598                if (columns.valueAt(i)) {
599                    if (column < length) {
600                        maxWidths[column] += extraSpace;
601                    } else {
602                        skipped++;
603                    }
604                }
605            }
606        } else {
607            for (int i = 0; i < count; i++) {
608                maxWidths[i] += extraSpace;
609            }
610
611            // we don't skip any column so we can return right away
612            return;
613        }
614
615        if (skipped > 0 && skipped < count) {
616            // reclaim any extra space we left to columns that don't exist
617            extraSpace = skipped * extraSpace / (count - skipped);
618            for (int i = 0; i < count; i++) {
619                int column = columns.keyAt(i);
620                if (columns.valueAt(i) && column < length) {
621                    if (extraSpace > maxWidths[column]) {
622                        maxWidths[column] = 0;
623                    } else {
624                        maxWidths[column] += extraSpace;
625                    }
626                }
627            }
628        }
629    }
630
631    /**
632     * {@inheritDoc}
633     */
634    @Override
635    public LayoutParams generateLayoutParams(AttributeSet attrs) {
636        return new TableLayout.LayoutParams(getContext(), attrs);
637    }
638
639    /**
640     * Returns a set of layout parameters with a width of
641     * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
642     * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
643     */
644    @Override
645    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
646        return new LayoutParams();
647    }
648
649    /**
650     * {@inheritDoc}
651     */
652    @Override
653    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
654        return p instanceof TableLayout.LayoutParams;
655    }
656
657    /**
658     * {@inheritDoc}
659     */
660    @Override
661    protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
662        return new LayoutParams(p);
663    }
664
665    @Override
666    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
667        super.onInitializeAccessibilityEvent(event);
668        event.setClassName(TableLayout.class.getName());
669    }
670
671    @Override
672    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
673        super.onInitializeAccessibilityNodeInfo(info);
674        info.setClassName(TableLayout.class.getName());
675    }
676
677    /**
678     * <p>This set of layout parameters enforces the width of each child to be
679     * {@link #MATCH_PARENT} and the height of each child to be
680     * {@link #WRAP_CONTENT}, but only if the height is not specified.</p>
681     */
682    @SuppressWarnings({"UnusedDeclaration"})
683    public static class LayoutParams extends LinearLayout.LayoutParams {
684        /**
685         * {@inheritDoc}
686         */
687        public LayoutParams(Context c, AttributeSet attrs) {
688            super(c, attrs);
689        }
690
691        /**
692         * {@inheritDoc}
693         */
694        public LayoutParams(int w, int h) {
695            super(MATCH_PARENT, h);
696        }
697
698        /**
699         * {@inheritDoc}
700         */
701        public LayoutParams(int w, int h, float initWeight) {
702            super(MATCH_PARENT, h, initWeight);
703        }
704
705        /**
706         * <p>Sets the child width to
707         * {@link android.view.ViewGroup.LayoutParams} and the child height to
708         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
709         */
710        public LayoutParams() {
711            super(MATCH_PARENT, WRAP_CONTENT);
712        }
713
714        /**
715         * {@inheritDoc}
716         */
717        public LayoutParams(ViewGroup.LayoutParams p) {
718            super(p);
719        }
720
721        /**
722         * {@inheritDoc}
723         */
724        public LayoutParams(MarginLayoutParams source) {
725            super(source);
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, int widthAttr, int heightAttr) {
741            this.width = MATCH_PARENT;
742            this.height = a.getLayoutDimension(heightAttr, WRAP_CONTENT);
743        }
744    }
745
746    /**
747     * <p>A pass-through listener acts upon the events and dispatches them
748     * to another listener. This allows the table layout to set its own internal
749     * hierarchy change listener without preventing the user to setup his.</p>
750     */
751    private class PassThroughHierarchyChangeListener implements
752            OnHierarchyChangeListener {
753        private OnHierarchyChangeListener mOnHierarchyChangeListener;
754
755        /**
756         * {@inheritDoc}
757         */
758        public void onChildViewAdded(View parent, View child) {
759            trackCollapsedColumns(child);
760
761            if (mOnHierarchyChangeListener != null) {
762                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
763            }
764        }
765
766        /**
767         * {@inheritDoc}
768         */
769        public void onChildViewRemoved(View parent, View child) {
770            if (mOnHierarchyChangeListener != null) {
771                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
772            }
773        }
774    }
775}
776