/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;

A layout that arranges its children horizontally. A TableRow should always be used as a child of a TableLayout. If a TableRow's parent is not a TableLayout, the TableRow will behave as an horizontal LinearLayout.

The children of a TableRow do not need to specify the layout_width and layout_height attributes in the XML file. TableRow always enforces those values to be respectively LayoutParams.MATCH_PARENT and LayoutParams.WRAP_CONTENT.

Also see android.widget.TableRow.LayoutParams for layout attributes

/** * <p>A layout that arranges its children horizontally. A TableRow should * always be used as a child of a {@link android.widget.TableLayout}. If a * TableRow's parent is not a TableLayout, the TableRow will behave as * an horizontal {@link android.widget.LinearLayout}.</p> * * <p>The children of a TableRow do not need to specify the * <code>layout_width</code> and <code>layout_height</code> attributes in the * XML file. TableRow always enforces those values to be respectively * {@link android.widget.TableLayout.LayoutParams#MATCH_PARENT} and * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p> * * <p> * Also see {@link TableRow.LayoutParams android.widget.TableRow.LayoutParams} * for layout attributes </p> */
public class TableRow extends LinearLayout { private int mNumColumns = 0; private int[] mColumnWidths; private int[] mConstrainedColumnWidths; private SparseIntArray mColumnToChildIndex; private ChildrenTracker mChildrenTracker;

Creates a new TableRow for the given context.

Params:
  • context – the application environment
/** * <p>Creates a new TableRow for the given context.</p> * * @param context the application environment */
public TableRow(Context context) { super(context); initTableRow(); }

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

Params:
  • context – the application environment
  • attrs – a collection of attributes
/** * <p>Creates a new TableRow for the given context and with the * specified set attributes.</p> * * @param context the application environment * @param attrs a collection of attributes */
public TableRow(Context context, AttributeSet attrs) { super(context, attrs); initTableRow(); } private void initTableRow() { OnHierarchyChangeListener oldListener = mOnHierarchyChangeListener; mChildrenTracker = new ChildrenTracker(); if (oldListener != null) { mChildrenTracker.setOnHierarchyChangeListener(oldListener); } super.setOnHierarchyChangeListener(mChildrenTracker); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { mChildrenTracker.setOnHierarchyChangeListener(listener); }

Collapses or restores a given column.

Params:
  • columnIndex – the index of the column
  • collapsed – true if the column must be collapsed, false otherwise {@hide}
/** * <p>Collapses or restores a given column.</p> * * @param columnIndex the index of the column * @param collapsed true if the column must be collapsed, false otherwise * {@hide} */
void setColumnCollapsed(int columnIndex, boolean collapsed) { final View child = getVirtualChildAt(columnIndex); if (child != null) { child.setVisibility(collapsed ? GONE : VISIBLE); } }
{@inheritDoc}
/** * {@inheritDoc} */
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // enforce horizontal layout measureHorizontal(widthMeasureSpec, heightMeasureSpec); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // enforce horizontal layout layoutHorizontal(l, t, r, b); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public View getVirtualChildAt(int i) { if (mColumnToChildIndex == null) { mapIndexAndColumns(); } final int deflectedIndex = mColumnToChildIndex.get(i, -1); if (deflectedIndex != -1) { return getChildAt(deflectedIndex); } return null; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int getVirtualChildCount() { if (mColumnToChildIndex == null) { mapIndexAndColumns(); } return mNumColumns; } private void mapIndexAndColumns() { if (mColumnToChildIndex == null) { int virtualCount = 0; final int count = getChildCount(); mColumnToChildIndex = new SparseIntArray(); final SparseIntArray columnToChild = mColumnToChildIndex; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); if (layoutParams.column >= virtualCount) { virtualCount = layoutParams.column; } for (int j = 0; j < layoutParams.span; j++) { columnToChild.put(virtualCount++, i); } } mNumColumns = virtualCount; } }
{@inheritDoc}
/** * {@inheritDoc} */
@Override int measureNullChild(int childIndex) { return mConstrainedColumnWidths[childIndex]; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { if (mConstrainedColumnWidths != null) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int measureMode = MeasureSpec.EXACTLY; int columnWidth = 0; final int span = lp.span; final int[] constrainedColumnWidths = mConstrainedColumnWidths; for (int i = 0; i < span; i++) { columnWidth += constrainedColumnWidths[childIndex + i]; } final int gravity = lp.gravity; final boolean isHorizontalGravity = Gravity.isHorizontal(gravity); if (isHorizontalGravity) { measureMode = MeasureSpec.AT_MOST; } // no need to care about padding here, // ViewGroup.getChildMeasureSpec() would get rid of it anyway // because of the EXACTLY measure spec we use int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, columnWidth - lp.leftMargin - lp.rightMargin), measureMode ); int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp .bottomMargin + totalHeight, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (isHorizontalGravity) { final int childWidth = child.getMeasuredWidth(); lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth; final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: // don't offset on X axis break; case Gravity.RIGHT: lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT]; break; case Gravity.CENTER_HORIZONTAL: lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2; break; } } else { lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0; } } else { // fail silently when column widths are not available super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); } }
{@inheritDoc}
/** * {@inheritDoc} */
@Override int getChildrenSkipCount(View child, int index) { LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); // when the span is 1 (default), we need to skip 0 child return layoutParams.span - 1; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override int getLocationOffset(View child) { return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION]; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override int getNextLocationOffset(View child) { return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT]; }

Measures the preferred width of each child, including its margins.

Params:
  • widthMeasureSpec – the width constraint imposed by our parent
Returns:an array of integers corresponding to the width of each cell, or column, in this row {@hide}
/** * <p>Measures the preferred width of each child, including its margins.</p> * * @param widthMeasureSpec the width constraint imposed by our parent * * @return an array of integers corresponding to the width of each cell, or * column, in this row * {@hide} */
int[] getColumnsWidths(int widthMeasureSpec, int heightMeasureSpec) { final int numColumns = getVirtualChildCount(); if (mColumnWidths == null || numColumns != mColumnWidths.length) { mColumnWidths = new int[numColumns]; } final int[] columnWidths = mColumnWidths; for (int i = 0; i < numColumns; i++) { final View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); if (layoutParams.span == 1) { int spec; switch (layoutParams.width) { case LayoutParams.WRAP_CONTENT: spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT); break; case LayoutParams.MATCH_PARENT: spec = MeasureSpec.makeSafeMeasureSpec( MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED); break; default: spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY); } child.measure(spec, spec); final int width = child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin; columnWidths[i] = width; } else { columnWidths[i] = 0; } } else { columnWidths[i] = 0; } } return columnWidths; }

Sets the width of all of the columns in this row. At layout time, this row sets a fixed width, as defined by columnWidths, on each child (or cell, or column.)

Params:
  • columnWidths – the fixed width of each column that this row must honor
Throws:
/** * <p>Sets the width of all of the columns in this row. At layout time, * this row sets a fixed width, as defined by <code>columnWidths</code>, * on each child (or cell, or column.)</p> * * @param columnWidths the fixed width of each column that this row must * honor * @throws IllegalArgumentException when columnWidths' length is smaller * than the number of children in this row * {@hide} */
void setColumnsWidthConstraints(int[] columnWidths) { if (columnWidths == null || columnWidths.length < getVirtualChildCount()) { throw new IllegalArgumentException( "columnWidths should be >= getVirtualChildCount()"); } mConstrainedColumnWidths = columnWidths; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new TableRow.LayoutParams(getContext(), attrs); }
Returns a set of layout parameters with a width of LayoutParams.MATCH_PARENT, a height of WRAP_CONTENT.WRAP_CONTENT and no spanning.
/** * Returns a set of layout parameters with a width of * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning. */
@Override protected LinearLayout.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof TableRow.LayoutParams; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override public CharSequence getAccessibilityClassName() { return TableRow.class.getName(); }

Set of layout parameters used in table rows.

See Also:
  • LayoutParams
@attrref android.R.styleable#TableRow_Cell_layout_column
@attrref android.R.styleable#TableRow_Cell_layout_span
/** * <p>Set of layout parameters used in table rows.</p> * * @see android.widget.TableLayout.LayoutParams * * @attr ref android.R.styleable#TableRow_Cell_layout_column * @attr ref android.R.styleable#TableRow_Cell_layout_span */
public static class LayoutParams extends LinearLayout.LayoutParams {

The column index of the cell represented by the widget.

/** * <p>The column index of the cell represented by the widget.</p> */
@ViewDebug.ExportedProperty(category = "layout") public int column;

The number of columns the widgets spans over.

/** * <p>The number of columns the widgets spans over.</p> */
@ViewDebug.ExportedProperty(category = "layout") public int span; private static final int LOCATION = 0; private static final int LOCATION_NEXT = 1; private int[] mOffset = new int[2];
{@inheritDoc}
/** * {@inheritDoc} */
public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TableRow_Cell); column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1); span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1); if (span <= 1) { span = 1; } a.recycle(); }

Sets the child width and the child height.

Params:
  • w – the desired width
  • h – the desired height
/** * <p>Sets the child width and the child height.</p> * * @param w the desired width * @param h the desired height */
public LayoutParams(int w, int h) { super(w, h); column = -1; span = 1; }

Sets the child width, height and weight.

Params:
  • w – the desired width
  • h – the desired height
  • initWeight – the desired weight
/** * <p>Sets the child width, height and weight.</p> * * @param w the desired width * @param h the desired height * @param initWeight the desired weight */
public LayoutParams(int w, int h, float initWeight) { super(w, h, initWeight); column = -1; span = 1; }

Sets the child width to LayoutParams and the child height to LayoutParams.WRAP_CONTENT.

/** * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams} * and the child height to * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p> */
public LayoutParams() { super(MATCH_PARENT, WRAP_CONTENT); column = -1; span = 1; }

Puts the view in the specified column.

Sets the child width to LayoutParams.MATCH_PARENT and the child height to LayoutParams.WRAP_CONTENT.

Params:
  • column – the column index for the view
/** * <p>Puts the view in the specified column.</p> * * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} * and the child height to * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p> * * @param column the column index for the view */
public LayoutParams(int column) { this(); this.column = column; }
{@inheritDoc}
/** * {@inheritDoc} */
public LayoutParams(ViewGroup.LayoutParams p) { super(p); }
{@inheritDoc}
/** * {@inheritDoc} */
public LayoutParams(MarginLayoutParams source) { super(source); } @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { // We don't want to force users to specify a layout_width if (a.hasValue(widthAttr)) { width = a.getLayoutDimension(widthAttr, "layout_width"); } else { width = MATCH_PARENT; } // We don't want to force users to specify a layout_height if (a.hasValue(heightAttr)) { height = a.getLayoutDimension(heightAttr, "layout_height"); } else { height = WRAP_CONTENT; } }
@hide
/** @hide */
@Override protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { super.encodeProperties(encoder); encoder.addProperty("layout:column", column); encoder.addProperty("layout:span", span); } } // special transparent hierarchy change listener private class ChildrenTracker implements OnHierarchyChangeListener { private OnHierarchyChangeListener listener; private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { this.listener = listener; } public void onChildViewAdded(View parent, View child) { // dirties the index to column map mColumnToChildIndex = null; if (this.listener != null) { this.listener.onChildViewAdded(parent, child); } } public void onChildViewRemoved(View parent, View child) { // dirties the index to column map mColumnToChildIndex = null; if (this.listener != null) { this.listener.onChildViewRemoved(parent, child); } } } }