/*
* 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.content.Context;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
//import android.view.ViewDebug;
/**
* <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#FILL_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;
/**
* <p>
* Creates a new TableRow for the given context.
* </p>
*
* @param context
* the application environment
*/
public TableRow(Context context) {
super(context);
initTableRow();
}
/**
* <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() {
if (getLayoutParams() == null) {
setLayoutParams(new TableRow.LayoutParams());
}
// OnHierarchyChangeListener oldListener = mOnHierarchyChangeListener;
// mChildrenTracker = new ChildrenTracker();
// if (oldListener != null) {
// mChildrenTracker.setOnHierarchyChangeListener(oldListener);
// }
// super.setOnHierarchyChangeListener(mChildrenTracker);
}
// /**
// * {@inheritDoc}
// */
// @Override
// public void setOnHierarchyChangeListener(OnHierarchyChangeListener
// listener) {
// mChildrenTracker.setOnHierarchyChangeListener(listener);
// }
/**
* <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) {
View child = getVirtualChildAt(columnIndex);
if (child != null) {
child.setVisibility(collapsed ? GONE : VISIBLE);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// enforce horizontal layout
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
/**
* {@inheritDoc}
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// enforce horizontal layout
layoutHorizontal();
}
/**
* {@inheritDoc}
*/
@Override
public View getVirtualChildAt(int i) {
// TODO: Following line should be removed once OnHierarchyChangeListener
// works and the OnHierarchyChangedListener above is uncommented.
mColumnToChildIndex = null;
if (mColumnToChildIndex == null) {
mapIndexAndColumns();
}
final int deflectedIndex = mColumnToChildIndex.get(i, -1);
if (deflectedIndex != -1) {
return getChildAt(deflectedIndex);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public int getVirtualChildCount() {
// TODO: Following line should be removed once OnHierarchyChangeListener
// works and the OnHierarchyChangedListener above is uncommented.
mColumnToChildIndex = null;
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}
*/
@Override
int measureNullChild(int childIndex) {
return mConstrainedColumnWidths[childIndex];
}
/**
* {@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, getPaddingTop()
+ getPaddingBottom() + 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;
switch (gravity & 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}
*/
@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}
*/
@Override
int getLocationOffset(View child) {
return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
}
/**
* {@inheritDoc}
*/
@Override
int getNextLocationOffset(View child) {
return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
}
/**
* <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) {
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.FILL_PARENT:
spec = MeasureSpec.makeMeasureSpec(0, 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;
}
/**
* <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}
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
/**
* Returns a set of layout parameters with a width of
* {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}, a height of
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
*/
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
/**
* {@inheritDoc}
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof TableRow.LayoutParams;
}
/**
* {@inheritDoc}
*/
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
/**
* <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 {
/**
* <p>
* The column index of the cell represented by the widget.
* </p>
*/
// @ViewDebug.ExportedProperty
public int column;
/**
* <p>
* The number of columns the widgets spans over.
* </p>
*/
// @ViewDebug.ExportedProperty
public int span;
private static final int LOCATION = 0;
private static final int LOCATION_NEXT = 1;
private int[] mOffset = new int[2];
/**
* {@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();
// }
/**
* <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;
}
/**
* <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;
}
/**
* <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(FILL_PARENT, WRAP_CONTENT);
column = -1;
span = 1;
}
/**
* <p>
* Puts the view in the specified column.
* </p>
*
* <p>
* Sets the child width to
* {@link android.view.ViewGroup.LayoutParams#FILL_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}
*/
public LayoutParams(ViewGroup.LayoutParams p) {
super(p);
}
/**
* {@inheritDoc}
*/
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
setBaseAttributes(attrs);
column = attrs.getAttributeIntValue(null, "layout_column", -1);
span = attrs.getAttributeIntValue(null, "layout_span", 1);
}
protected void setBaseAttributes(AttributeSet attrs) {
// We don't want to force users to specify a layout_width
String str = attrs.getAttributeValue(null, "layout_width");
if (str == null || str.length() == 0) {
width = FILL_PARENT;
}
// We don't want to force users to specify a layout_height
str = attrs.getAttributeValue(null, "layout_height");
if (str == null || str.length() == 0) {
height = WRAP_CONTENT;
}
}
// }
//
// // 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);
// }
// }
}
}