| /* |
| * 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; |
| |
| /** |
| * <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; |
| |
| /** |
| * <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() { |
| 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) { |
| final 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(l, t, r, b); |
| } |
| |
| /** |
| * {@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} |
| */ |
| @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} |
| */ |
| @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, |
| 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} |
| */ |
| @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, 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; |
| } |
| |
| /** |
| * <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 TableRow.LayoutParams(getContext(), attrs); |
| } |
| |
| /** |
| * 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} |
| */ |
| @Override |
| protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { |
| return p instanceof TableRow.LayoutParams; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { |
| return new LayoutParams(p); |
| } |
| |
| @Override |
| public CharSequence getAccessibilityClassName() { |
| return TableRow.class.getName(); |
| } |
| |
| /** |
| * <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(category = "layout") |
| public int column; |
| |
| /** |
| * <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} |
| */ |
| 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(MATCH_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#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} |
| */ |
| public LayoutParams(ViewGroup.LayoutParams p) { |
| super(p); |
| } |
| |
| /** |
| * {@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 */ |
| @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); |
| } |
| } |
| } |
| } |