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