FileDocCategorySizeDatePackage
TableLayout.javaAPI DocAndroid 1.5 API26800Wed May 06 22:41:56 BST 2009android.widget

TableLayout

public class TableLayout extends LinearLayout

A layout that arranges its children into rows and columns. A TableLayout consists of a number of {@link android.widget.TableRow} objects, each defining a row (actually, you can have other children, which will be explained below). TableLayout containers do not display border lines for their rows, columns, or cells. Each row has zero or more cells; each cell can hold one {@link android.view.View View} object. The table has as many columns as the row with the most cells. A table can leave cells empty. Cells can span columns, as they can in HTML.

The width of a column is defined by the row with the widest cell in that column. However, a TableLayout can specify certain columns as shrinkable or stretchable by calling {@link #setColumnShrinkable(int, boolean) setColumnShrinkable()} or {@link #setColumnStretchable(int, boolean) setColumnStretchable()}. If marked as shrinkable, the column width can be shrunk to fit the table into its parent object. If marked as stretchable, it can expand in width to fit any extra space. The total width of the table is defined by its parent container. It is important to remember that a column can be both shrinkable and stretchable. In such a situation, the column will change its size to always use up the available space, but never more. Finally, you can hide a column by calling {@link #setColumnCollapsed(int,boolean) setColumnCollapsed()}.

The children of a TableLayout cannot specify the layout_width attribute. Width is always FILL_PARENT. However, the layout_height attribute can be defined by a child; default value is {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}. If the child is a {@link android.widget.TableRow}, then the height is always {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.

Cells must be added to a row in increasing column order, both in code and XML. Column numbers are zero-based. If you don't specify a column number for a child cell, it will autoincrement to the next available column. If you skip a column number, it will be considered an empty cell in that row. See the TableLayout examples in ApiDemos for examples of creating tables in XML.

Although the typical child of a TableLayout is a TableRow, you can actually use any View subclass as a direct child of TableLayout. The View will be displayed as a single row that spans all the table columns.

Fields Summary
private int[]
mMaxWidths
private android.util.SparseBooleanArray
mStretchableColumns
private android.util.SparseBooleanArray
mShrinkableColumns
private android.util.SparseBooleanArray
mCollapsedColumns
private boolean
mShrinkAllColumns
private boolean
mStretchAllColumns
private PassThroughHierarchyChangeListener
mPassThroughListener
private boolean
mInitialized
Constructors Summary
public TableLayout(android.content.Context context)

Creates a new TableLayout for the given context.

param
context the application environment

        super(context);
        initTableLayout();
    
public TableLayout(android.content.Context context, android.util.AttributeSet attrs)

Creates a new TableLayout for the given context and with the specified set attributes.

param
context the application environment
param
attrs a collection of attributes

        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TableLayout);

        String stretchedColumns = a.getString(R.styleable.TableLayout_stretchColumns);
        if (stretchedColumns != null) {
            if (stretchedColumns.charAt(0) == '*") {
                mStretchAllColumns = true;
            } else {
                mStretchableColumns = parseColumns(stretchedColumns);
            }
        }

        String shrinkedColumns = a.getString(R.styleable.TableLayout_shrinkColumns);
        if (shrinkedColumns != null) {
            if (shrinkedColumns.charAt(0) == '*") {
                mShrinkAllColumns = true;
            } else {
                mShrinkableColumns = parseColumns(shrinkedColumns);
            }
        }

        String collapsedColumns = a.getString(R.styleable.TableLayout_collapseColumns);
        if (collapsedColumns != null) {
            mCollapsedColumns = parseColumns(collapsedColumns);
        }

        a.recycle();
        initTableLayout();
    
Methods Summary
public voidaddView(android.view.View child)
{@inheritDoc}

        super.addView(child);
        requestRowsLayout();
    
public voidaddView(android.view.View child, int index)
{@inheritDoc}

        super.addView(child, index);
        requestRowsLayout();
    
public voidaddView(android.view.View child, ViewGroup.LayoutParams params)
{@inheritDoc}

        super.addView(child, params);
        requestRowsLayout();
    
public voidaddView(android.view.View child, int index, ViewGroup.LayoutParams params)
{@inheritDoc}

        super.addView(child, index, params);
        requestRowsLayout();
    
protected booleancheckLayoutParams(ViewGroup.LayoutParams p)
{@inheritDoc}

        return p instanceof TableLayout.LayoutParams;
    
private voidfindLargestCells(int widthMeasureSpec)

Finds the largest cell in each column. For each column, the width of the largest cell is applied to all the other cells.

param
widthMeasureSpec the measure constraint imposed by our parent

        boolean firstRow = true;

        // find the maximum width for each column
        // the total number of columns is dynamically changed if we find
        // wider rows as we go through the children
        // the array is reused for each layout operation; the array can grow
        // but never shrinks. Unused extra cells in the array are just ignored
        // this behavior avoids to unnecessary grow the array after the first
        // layout operation
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            if (child instanceof TableRow) {
                final TableRow row = (TableRow) child;
                // forces the row's height
                final ViewGroup.LayoutParams layoutParams = row.getLayoutParams();
                layoutParams.height = LayoutParams.WRAP_CONTENT;

                final int[] widths = row.getColumnsWidths(widthMeasureSpec);
                final int newLength = widths.length;
                // this is the first row, we just need to copy the values
                if (firstRow) {
                    if (mMaxWidths == null || mMaxWidths.length != newLength) {
                        mMaxWidths = new int[newLength];
                    }
                    System.arraycopy(widths, 0, mMaxWidths, 0, newLength);
                    firstRow = false;
                } else {
                    int length = mMaxWidths.length;
                    final int difference = newLength - length;
                    // the current row is wider than the previous rows, so
                    // we just grow the array and copy the values
                    if (difference > 0) {
                        final int[] oldMaxWidths = mMaxWidths;
                        mMaxWidths = new int[newLength];
                        System.arraycopy(oldMaxWidths, 0, mMaxWidths, 0,
                                oldMaxWidths.length);
                        System.arraycopy(widths, oldMaxWidths.length,
                                mMaxWidths, oldMaxWidths.length, difference);
                    }

                    // the row is narrower or of the same width as the previous
                    // rows, so we find the maximum width for each column
                    // if the row is narrower than the previous ones,
                    // difference will be negative
                    final int[] maxWidths = mMaxWidths;
                    length = Math.min(length, newLength);
                    for (int j = 0; j < length; j++) {
                        maxWidths[j] = Math.max(maxWidths[j], widths[j]);
                    }
                }
            }
        }
    
protected LinearLayout.LayoutParamsgenerateDefaultLayoutParams()
Returns a set of layout parameters with a width of {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}, and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.

        return new LayoutParams();
    
public android.widget.TableLayout$LayoutParamsgenerateLayoutParams(android.util.AttributeSet attrs)
{@inheritDoc}

        return new TableLayout.LayoutParams(getContext(), attrs);
    
protected LinearLayout.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams p)
{@inheritDoc}

        return new LayoutParams(p);
    
private voidinitTableLayout()

Performs initialization common to prorgrammatic use and XML use of this widget.

        if (mCollapsedColumns == null) {
            mCollapsedColumns = new SparseBooleanArray();
        }
        if (mStretchableColumns == null) {
            mStretchableColumns = new SparseBooleanArray();
        }
        if (mShrinkableColumns == null) {
            mShrinkableColumns = new SparseBooleanArray();
        }

        mPassThroughListener = new PassThroughHierarchyChangeListener();
        // make sure to call the parent class method to avoid potential
        // infinite loops
        super.setOnHierarchyChangeListener(mPassThroughListener);

        mInitialized = true;
    
public booleanisColumnCollapsed(int columnIndex)

Returns the collapsed state of the specified column.

param
columnIndex the index of the column
return
true if the column is collapsed, false otherwise

        return mCollapsedColumns.get(columnIndex);
    
public booleanisColumnShrinkable(int columnIndex)

Returns whether the specified column is shrinkable or not.

param
columnIndex the index of the column
return
true if the column is shrinkable, false otherwise. Default is false.

        return mShrinkAllColumns || mShrinkableColumns.get(columnIndex);
    
public booleanisColumnStretchable(int columnIndex)

Returns whether the specified column is stretchable or not.

param
columnIndex the index of the column
return
true if the column is stretchable, false otherwise

        return mStretchAllColumns || mStretchableColumns.get(columnIndex);
    
public booleanisShrinkAllColumns()

Indicates whether all columns are shrinkable or not.

return
true if all columns are shrinkable, false otherwise

        return mShrinkAllColumns;
    
public booleanisStretchAllColumns()

Indicates whether all columns are stretchable or not.

return
true if all columns are stretchable, false otherwise

        return mStretchAllColumns;
    
voidmeasureChildBeforeLayout(android.view.View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)
{@inheritDoc}

        // when the measured child is a table row, we force the width of its
        // children with the widths computed in findLargestCells()
        if (child instanceof TableRow) {
            ((TableRow) child).setColumnsWidthConstraints(mMaxWidths);
        }

        super.measureChildBeforeLayout(child, childIndex,
                widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
    
voidmeasureVertical(int widthMeasureSpec, int heightMeasureSpec)
{@inheritDoc}

        findLargestCells(widthMeasureSpec);
        shrinkAndStretchColumns(widthMeasureSpec);

        super.measureVertical(widthMeasureSpec, heightMeasureSpec);
    
private voidmutateColumnsWidth(android.util.SparseBooleanArray columns, boolean allColumns, int size, int totalWidth)

        int skipped = 0;
        final int[] maxWidths = mMaxWidths;
        final int length = maxWidths.length;
        final int count = allColumns ? length : columns.size();
        final int totalExtraSpace = size - totalWidth;
        int extraSpace = totalExtraSpace / count;

        if (!allColumns) {
            for (int i = 0; i < count; i++) {
                int column = columns.keyAt(i);
                if (columns.valueAt(i)) {
                    if (column < length) {
                        maxWidths[column] += extraSpace;
                    } else {
                        skipped++;
                    }
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                maxWidths[i] += extraSpace;
            }

            // we don't skip any column so we can return right away
            return;
        }

        if (skipped > 0 && skipped < count) {
            // reclaim any extra space we left to columns that don't exist
            extraSpace = skipped * extraSpace / (count - skipped);
            for (int i = 0; i < count; i++) {
                int column = columns.keyAt(i);
                if (columns.valueAt(i) && column < length) {
                    if (extraSpace > maxWidths[column]) {
                        maxWidths[column] = 0;
                    } else {
                        maxWidths[column] += extraSpace;
                    }
                }
            }
        }
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)
{@inheritDoc}

        // enforce vertical layout
        layoutVertical();
    
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)
{@inheritDoc}

        // enforce vertical layout
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    
private static android.util.SparseBooleanArrayparseColumns(java.lang.String sequence)

Parses a sequence of columns ids defined in a CharSequence with the following pattern (regex): \d+(\s*,\s*\d+)*

Examples: "1" or "13, 7, 6" or "".

The result of the parsing is stored in a sparse boolean array. The parsed column ids are used as the keys of the sparse array. The values are always true.

param
sequence a sequence of column ids, can be empty but not null
return
a sparse array of boolean mapping column indexes to the columns collapse state

        SparseBooleanArray columns = new SparseBooleanArray();
        Pattern pattern = Pattern.compile("\\s*,\\s*");
        String[] columnDefs = pattern.split(sequence);

        for (String columnIdentifier : columnDefs) {
            try {
                int columnIndex = Integer.parseInt(columnIdentifier);
                // only valid, i.e. positive, columns indexes are handled
                if (columnIndex >= 0) {
                    // putting true in this sparse array indicates that the
                    // column index was defined in the XML file
                    columns.put(columnIndex, true);
                }
            } catch (NumberFormatException e) {
                // we just ignore columns that don't exist
            }
        }

        return columns;
    
public voidrequestLayout()
{@inheritDoc}

        if (mInitialized) {
            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                getChildAt(i).forceLayout();
            }
        }

        super.requestLayout();
    
private voidrequestRowsLayout()

        if (mInitialized) {
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                getChildAt(i).requestLayout();
            }
        }
    
public voidsetColumnCollapsed(int columnIndex, boolean isCollapsed)

Collapses or restores a given column. When collapsed, a column does not appear on screen and the extra space is reclaimed by the other columns. A column is collapsed/restored only when it belongs to a {@link android.widget.TableRow}.

Calling this method requests a layout operation.

param
columnIndex the index of the column
param
isCollapsed true if the column must be collapsed, false otherwise
attr
ref android.R.styleable#TableLayout_collapseColumns

        // update the collapse status of the column
        mCollapsedColumns.put(columnIndex, isCollapsed);

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i);
            if (view instanceof TableRow) {
                ((TableRow) view).setColumnCollapsed(columnIndex, isCollapsed);
            }
        }

        requestRowsLayout();
    
public voidsetColumnShrinkable(int columnIndex, boolean isShrinkable)

Makes the given column shrinkable or not. When a row is too wide, the table can reclaim extra space from shrinkable columns.

Calling this method requests a layout operation.

param
columnIndex the index of the column
param
isShrinkable true if the column must be shrinkable, false otherwise. Default is false.
attr
ref android.R.styleable#TableLayout_shrinkColumns

        mShrinkableColumns.put(columnIndex, isShrinkable);
        requestRowsLayout();
    
public voidsetColumnStretchable(int columnIndex, boolean isStretchable)

Makes the given column stretchable or not. When stretchable, a column takes up as much as available space as possible in its row.

Calling this method requests a layout operation.

param
columnIndex the index of the column
param
isStretchable true if the column must be stretchable, false otherwise. Default is false.
attr
ref android.R.styleable#TableLayout_stretchColumns

        mStretchableColumns.put(columnIndex, isStretchable);
        requestRowsLayout();
    
public voidsetOnHierarchyChangeListener(OnHierarchyChangeListener listener)
{@inheritDoc}

        // the user listener is delegated to our pass-through listener
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    
public voidsetShrinkAllColumns(boolean shrinkAllColumns)

Convenience method to mark all columns as shrinkable.

param
shrinkAllColumns true to mark all columns shrinkable
attr
ref android.R.styleable#TableLayout_shrinkColumns

        mShrinkAllColumns = shrinkAllColumns;
    
public voidsetStretchAllColumns(boolean stretchAllColumns)

Convenience method to mark all columns as stretchable.

param
stretchAllColumns true to mark all columns stretchable
attr
ref android.R.styleable#TableLayout_stretchColumns

        mStretchAllColumns = stretchAllColumns;
    
private voidshrinkAndStretchColumns(int widthMeasureSpec)

Shrinks the columns if their total width is greater than the width allocated by widthMeasureSpec. When the total width is less than the allocated width, this method attempts to stretch columns to fill the remaining space.

param
widthMeasureSpec the width measure specification as indicated by this widget's parent

        // when we have no row, mMaxWidths is not initialized and the loop
        // below could cause a NPE
        if (mMaxWidths == null) {
            return;
        }

        // should we honor AT_MOST, EXACTLY and UNSPECIFIED?
        int totalWidth = 0;
        for (int width : mMaxWidths) {
            totalWidth += width;
        }

        int size = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;

        if ((totalWidth > size) && (mShrinkAllColumns || mShrinkableColumns.size() > 0)) {
            // oops, the largest columns are wider than the row itself
            // fairly redistribute the row's widh among the columns
            mutateColumnsWidth(mShrinkableColumns, mShrinkAllColumns, size, totalWidth);
        } else if ((totalWidth < size) && (mStretchAllColumns || mStretchableColumns.size() > 0)) {
            // if we have some space left, we distribute it among the
            // expandable columns
            mutateColumnsWidth(mStretchableColumns, mStretchAllColumns, size, totalWidth);
        }
    
private voidtrackCollapsedColumns(android.view.View child)

Applies the columns collapse status to a new row added to this table. This method is invoked by PassThroughHierarchyChangeListener upon child insertion.

This method only applies to {@link android.widget.TableRow} instances.

param
child the newly added child

        if (child instanceof TableRow) {
            final TableRow row = (TableRow) child;
            final SparseBooleanArray collapsedColumns = mCollapsedColumns;
            final int count = collapsedColumns.size();
            for (int i = 0; i < count; i++) {
                int columnIndex = collapsedColumns.keyAt(i);
                boolean isCollapsed = collapsedColumns.valueAt(i);
                // the collapse status is set only when the column should be
                // collapsed; otherwise, this might affect the default
                // visibility of the row's children
                if (isCollapsed) {
                    row.setColumnCollapsed(columnIndex, isCollapsed);
                }
            }
        }