TableLayoutpublic 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.
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.
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 void | addView(android.view.View child){@inheritDoc}
super.addView(child);
requestRowsLayout();
| public void | addView(android.view.View child, int index){@inheritDoc}
super.addView(child, index);
requestRowsLayout();
| public void | addView(android.view.View child, ViewGroup.LayoutParams params){@inheritDoc}
super.addView(child, params);
requestRowsLayout();
| public void | addView(android.view.View child, int index, ViewGroup.LayoutParams params){@inheritDoc}
super.addView(child, index, params);
requestRowsLayout();
| protected boolean | checkLayoutParams(ViewGroup.LayoutParams p){@inheritDoc}
return p instanceof TableLayout.LayoutParams;
| private void | findLargestCells(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.
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.LayoutParams | generateDefaultLayoutParams()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$LayoutParams | generateLayoutParams(android.util.AttributeSet attrs){@inheritDoc}
return new TableLayout.LayoutParams(getContext(), attrs);
| protected LinearLayout.LayoutParams | generateLayoutParams(ViewGroup.LayoutParams p){@inheritDoc}
return new LayoutParams(p);
| private void | initTableLayout()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 boolean | isColumnCollapsed(int columnIndex)Returns the collapsed state of the specified column.
return mCollapsedColumns.get(columnIndex);
| public boolean | isColumnShrinkable(int columnIndex)Returns whether the specified column is shrinkable or not.
return mShrinkAllColumns || mShrinkableColumns.get(columnIndex);
| public boolean | isColumnStretchable(int columnIndex)Returns whether the specified column is stretchable or not.
return mStretchAllColumns || mStretchableColumns.get(columnIndex);
| public boolean | isShrinkAllColumns()Indicates whether all columns are shrinkable or not.
return mShrinkAllColumns;
| public boolean | isStretchAllColumns()Indicates whether all columns are stretchable or not.
return mStretchAllColumns;
| void | measureChildBeforeLayout(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);
| void | measureVertical(int widthMeasureSpec, int heightMeasureSpec){@inheritDoc}
findLargestCells(widthMeasureSpec);
shrinkAndStretchColumns(widthMeasureSpec);
super.measureVertical(widthMeasureSpec, heightMeasureSpec);
| private void | mutateColumnsWidth(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 void | onLayout(boolean changed, int l, int t, int r, int b){@inheritDoc}
// enforce vertical layout
layoutVertical();
| protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec){@inheritDoc}
// enforce vertical layout
measureVertical(widthMeasureSpec, heightMeasureSpec);
| private static android.util.SparseBooleanArray | parseColumns(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.
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 void | requestLayout(){@inheritDoc}
if (mInitialized) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).forceLayout();
}
}
super.requestLayout();
| private void | requestRowsLayout()
if (mInitialized) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).requestLayout();
}
}
| public void | setColumnCollapsed(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.
// 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 void | setColumnShrinkable(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.
mShrinkableColumns.put(columnIndex, isShrinkable);
requestRowsLayout();
| public void | setColumnStretchable(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.
mStretchableColumns.put(columnIndex, isStretchable);
requestRowsLayout();
| public void | setOnHierarchyChangeListener(OnHierarchyChangeListener listener){@inheritDoc}
// the user listener is delegated to our pass-through listener
mPassThroughListener.mOnHierarchyChangeListener = listener;
| public void | setShrinkAllColumns(boolean shrinkAllColumns)Convenience method to mark all columns as shrinkable.
mShrinkAllColumns = shrinkAllColumns;
| public void | setStretchAllColumns(boolean stretchAllColumns)Convenience method to mark all columns as stretchable.
mStretchAllColumns = stretchAllColumns;
| private void | shrinkAndStretchColumns(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.
// 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 void | trackCollapsedColumns(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.
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);
}
}
}
|
|