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