Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.util.AttributeSet;
     22 import android.util.SparseIntArray;
     23 import android.view.Gravity;
     24 import android.view.View;
     25 import android.view.ViewDebug;
     26 import android.view.ViewGroup;
     27 
     28 
     29 /**
     30  * <p>A layout that arranges its children horizontally. A TableRow should
     31  * always be used as a child of a {@link android.widget.TableLayout}. If a
     32  * TableRow's parent is not a TableLayout, the TableRow will behave as
     33  * an horizontal {@link android.widget.LinearLayout}.</p>
     34  *
     35  * <p>The children of a TableRow do not need to specify the
     36  * <code>layout_width</code> and <code>layout_height</code> attributes in the
     37  * XML file. TableRow always enforces those values to be respectively
     38  * {@link android.widget.TableLayout.LayoutParams#MATCH_PARENT} and
     39  * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
     40  *
     41  * <p>
     42  * Also see {@link TableRow.LayoutParams android.widget.TableRow.LayoutParams}
     43  * for layout attributes </p>
     44  */
     45 public class TableRow extends LinearLayout {
     46     private int mNumColumns = 0;
     47     private int[] mColumnWidths;
     48     private int[] mConstrainedColumnWidths;
     49     private SparseIntArray mColumnToChildIndex;
     50 
     51     private ChildrenTracker mChildrenTracker;
     52 
     53     /**
     54      * <p>Creates a new TableRow for the given context.</p>
     55      *
     56      * @param context the application environment
     57      */
     58     public TableRow(Context context) {
     59         super(context);
     60         initTableRow();
     61     }
     62 
     63     /**
     64      * <p>Creates a new TableRow for the given context and with the
     65      * specified set attributes.</p>
     66      *
     67      * @param context the application environment
     68      * @param attrs a collection of attributes
     69      */
     70     public TableRow(Context context, AttributeSet attrs) {
     71         super(context, attrs);
     72         initTableRow();
     73     }
     74 
     75     private void initTableRow() {
     76         OnHierarchyChangeListener oldListener = mOnHierarchyChangeListener;
     77         mChildrenTracker = new ChildrenTracker();
     78         if (oldListener != null) {
     79             mChildrenTracker.setOnHierarchyChangeListener(oldListener);
     80         }
     81         super.setOnHierarchyChangeListener(mChildrenTracker);
     82     }
     83 
     84     /**
     85      * {@inheritDoc}
     86      */
     87     @Override
     88     public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
     89         mChildrenTracker.setOnHierarchyChangeListener(listener);
     90     }
     91 
     92     /**
     93      * <p>Collapses or restores a given column.</p>
     94      *
     95      * @param columnIndex the index of the column
     96      * @param collapsed true if the column must be collapsed, false otherwise
     97      * {@hide}
     98      */
     99     void setColumnCollapsed(int columnIndex, boolean collapsed) {
    100         View child = getVirtualChildAt(columnIndex);
    101         if (child != null) {
    102             child.setVisibility(collapsed ? GONE : VISIBLE);
    103         }
    104     }
    105 
    106     /**
    107      * {@inheritDoc}
    108      */
    109     @Override
    110     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    111         // enforce horizontal layout
    112         measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    113     }
    114 
    115     /**
    116      * {@inheritDoc}
    117      */
    118     @Override
    119     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    120         // enforce horizontal layout
    121         layoutHorizontal();
    122     }
    123 
    124     /**
    125      * {@inheritDoc}
    126      */
    127     @Override
    128     public View getVirtualChildAt(int i) {
    129         if (mColumnToChildIndex == null) {
    130             mapIndexAndColumns();
    131         }
    132 
    133         final int deflectedIndex = mColumnToChildIndex.get(i, -1);
    134         if (deflectedIndex != -1) {
    135             return getChildAt(deflectedIndex);
    136         }
    137 
    138         return null;
    139     }
    140 
    141     /**
    142      * {@inheritDoc}
    143      */
    144     @Override
    145     public int getVirtualChildCount() {
    146         if (mColumnToChildIndex == null) {
    147             mapIndexAndColumns();
    148         }
    149         return mNumColumns;
    150     }
    151 
    152     private void mapIndexAndColumns() {
    153         if (mColumnToChildIndex == null) {
    154             int virtualCount = 0;
    155             final int count = getChildCount();
    156 
    157             mColumnToChildIndex = new SparseIntArray();
    158             final SparseIntArray columnToChild = mColumnToChildIndex;
    159 
    160             for (int i = 0; i < count; i++) {
    161                 final View child = getChildAt(i);
    162                 final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
    163 
    164                 if (layoutParams.column >= virtualCount) {
    165                     virtualCount = layoutParams.column;
    166                 }
    167 
    168                 for (int j = 0; j < layoutParams.span; j++) {
    169                     columnToChild.put(virtualCount++, i);
    170                 }
    171             }
    172 
    173             mNumColumns = virtualCount;
    174         }
    175     }
    176 
    177     /**
    178      * {@inheritDoc}
    179      */
    180     @Override
    181     int measureNullChild(int childIndex) {
    182         return mConstrainedColumnWidths[childIndex];
    183     }
    184 
    185     /**
    186      * {@inheritDoc}
    187      */
    188     @Override
    189     void measureChildBeforeLayout(View child, int childIndex,
    190             int widthMeasureSpec, int totalWidth,
    191             int heightMeasureSpec, int totalHeight) {
    192         if (mConstrainedColumnWidths != null) {
    193             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    194 
    195             int measureMode = MeasureSpec.EXACTLY;
    196             int columnWidth = 0;
    197 
    198             final int span = lp.span;
    199             final int[] constrainedColumnWidths = mConstrainedColumnWidths;
    200             for (int i = 0; i < span; i++) {
    201                 columnWidth += constrainedColumnWidths[childIndex + i];
    202             }
    203 
    204             final int gravity = lp.gravity;
    205             final boolean isHorizontalGravity = Gravity.isHorizontal(gravity);
    206 
    207             if (isHorizontalGravity) {
    208                 measureMode = MeasureSpec.AT_MOST;
    209             }
    210 
    211             // no need to care about padding here,
    212             // ViewGroup.getChildMeasureSpec() would get rid of it anyway
    213             // because of the EXACTLY measure spec we use
    214             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
    215                     Math.max(0, columnWidth - lp.leftMargin - lp.rightMargin), measureMode
    216             );
    217             int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
    218                     mPaddingTop + mPaddingBottom + lp.topMargin +
    219                     lp .bottomMargin + totalHeight, lp.height);
    220 
    221             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    222 
    223             if (isHorizontalGravity) {
    224                 final int childWidth = child.getMeasuredWidth();
    225                 lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth;
    226 
    227                 final int layoutDirection = getResolvedLayoutDirection();
    228                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
    229                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    230                     case Gravity.LEFT:
    231                         // don't offset on X axis
    232                         break;
    233                     case Gravity.RIGHT:
    234                         lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT];
    235                         break;
    236                     case Gravity.CENTER_HORIZONTAL:
    237                         lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2;
    238                         break;
    239                 }
    240             } else {
    241                 lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0;
    242             }
    243         } else {
    244             // fail silently when column widths are not available
    245             super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec,
    246                     totalWidth, heightMeasureSpec, totalHeight);
    247         }
    248     }
    249 
    250     /**
    251      * {@inheritDoc}
    252      */
    253     @Override
    254     int getChildrenSkipCount(View child, int index) {
    255         LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
    256 
    257         // when the span is 1 (default), we need to skip 0 child
    258         return layoutParams.span - 1;
    259     }
    260 
    261     /**
    262      * {@inheritDoc}
    263      */
    264     @Override
    265     int getLocationOffset(View child) {
    266         return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
    267     }
    268 
    269     /**
    270      * {@inheritDoc}
    271      */
    272     @Override
    273     int getNextLocationOffset(View child) {
    274         return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
    275     }
    276 
    277     /**
    278      * <p>Measures the preferred width of each child, including its margins.</p>
    279      *
    280      * @param widthMeasureSpec the width constraint imposed by our parent
    281      *
    282      * @return an array of integers corresponding to the width of each cell, or
    283      *         column, in this row
    284      * {@hide}
    285      */
    286     int[] getColumnsWidths(int widthMeasureSpec) {
    287         final int numColumns = getVirtualChildCount();
    288         if (mColumnWidths == null || numColumns != mColumnWidths.length) {
    289             mColumnWidths = new int[numColumns];
    290         }
    291 
    292         final int[] columnWidths = mColumnWidths;
    293 
    294         for (int i = 0; i < numColumns; i++) {
    295             final View child = getVirtualChildAt(i);
    296             if (child != null && child.getVisibility() != GONE) {
    297                 final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
    298                 if (layoutParams.span == 1) {
    299                     int spec;
    300                     switch (layoutParams.width) {
    301                         case LayoutParams.WRAP_CONTENT:
    302                             spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
    303                             break;
    304                         case LayoutParams.MATCH_PARENT:
    305                             spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    306                             break;
    307                         default:
    308                             spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
    309                     }
    310                     child.measure(spec, spec);
    311 
    312                     final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
    313                             layoutParams.rightMargin;
    314                     columnWidths[i] = width;
    315                 } else {
    316                     columnWidths[i] = 0;
    317                 }
    318             } else {
    319                 columnWidths[i] = 0;
    320             }
    321         }
    322 
    323         return columnWidths;
    324     }
    325 
    326     /**
    327      * <p>Sets the width of all of the columns in this row. At layout time,
    328      * this row sets a fixed width, as defined by <code>columnWidths</code>,
    329      * on each child (or cell, or column.)</p>
    330      *
    331      * @param columnWidths the fixed width of each column that this row must
    332      *                     honor
    333      * @throws IllegalArgumentException when columnWidths' length is smaller
    334      *         than the number of children in this row
    335      * {@hide}
    336      */
    337     void setColumnsWidthConstraints(int[] columnWidths) {
    338         if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
    339             throw new IllegalArgumentException(
    340                     "columnWidths should be >= getVirtualChildCount()");
    341         }
    342 
    343         mConstrainedColumnWidths = columnWidths;
    344     }
    345 
    346     /**
    347      * {@inheritDoc}
    348      */
    349     @Override
    350     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    351         return new TableRow.LayoutParams(getContext(), attrs);
    352     }
    353 
    354     /**
    355      * Returns a set of layout parameters with a width of
    356      * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
    357      * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
    358      */
    359     @Override
    360     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
    361         return new LayoutParams();
    362     }
    363 
    364     /**
    365      * {@inheritDoc}
    366      */
    367     @Override
    368     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    369         return p instanceof TableRow.LayoutParams;
    370     }
    371 
    372     /**
    373      * {@inheritDoc}
    374      */
    375     @Override
    376     protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    377         return new LayoutParams(p);
    378     }
    379 
    380     /**
    381      * <p>Set of layout parameters used in table rows.</p>
    382      *
    383      * @see android.widget.TableLayout.LayoutParams
    384      *
    385      * @attr ref android.R.styleable#TableRow_Cell_layout_column
    386      * @attr ref android.R.styleable#TableRow_Cell_layout_span
    387      */
    388     public static class LayoutParams extends LinearLayout.LayoutParams {
    389         /**
    390          * <p>The column index of the cell represented by the widget.</p>
    391          */
    392         @ViewDebug.ExportedProperty(category = "layout")
    393         public int column;
    394 
    395         /**
    396          * <p>The number of columns the widgets spans over.</p>
    397          */
    398         @ViewDebug.ExportedProperty(category = "layout")
    399         public int span;
    400 
    401         private static final int LOCATION = 0;
    402         private static final int LOCATION_NEXT = 1;
    403 
    404         private int[] mOffset = new int[2];
    405 
    406         /**
    407          * {@inheritDoc}
    408          */
    409         public LayoutParams(Context c, AttributeSet attrs) {
    410             super(c, attrs);
    411 
    412             TypedArray a =
    413                     c.obtainStyledAttributes(attrs,
    414                             com.android.internal.R.styleable.TableRow_Cell);
    415 
    416             column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
    417             span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
    418             if (span <= 1) {
    419                 span = 1;
    420             }
    421 
    422             a.recycle();
    423         }
    424 
    425         /**
    426          * <p>Sets the child width and the child height.</p>
    427          *
    428          * @param w the desired width
    429          * @param h the desired height
    430          */
    431         public LayoutParams(int w, int h) {
    432             super(w, h);
    433             column = -1;
    434             span = 1;
    435         }
    436 
    437         /**
    438          * <p>Sets the child width, height and weight.</p>
    439          *
    440          * @param w the desired width
    441          * @param h the desired height
    442          * @param initWeight the desired weight
    443          */
    444         public LayoutParams(int w, int h, float initWeight) {
    445             super(w, h, initWeight);
    446             column = -1;
    447             span = 1;
    448         }
    449 
    450         /**
    451          * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
    452          * and the child height to
    453          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
    454          */
    455         public LayoutParams() {
    456             super(MATCH_PARENT, WRAP_CONTENT);
    457             column = -1;
    458             span = 1;
    459         }
    460 
    461         /**
    462          * <p>Puts the view in the specified column.</p>
    463          *
    464          * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
    465          * and the child height to
    466          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
    467          *
    468          * @param column the column index for the view
    469          */
    470         public LayoutParams(int column) {
    471             this();
    472             this.column = column;
    473         }
    474 
    475         /**
    476          * {@inheritDoc}
    477          */
    478         public LayoutParams(ViewGroup.LayoutParams p) {
    479             super(p);
    480         }
    481 
    482         /**
    483          * {@inheritDoc}
    484          */
    485         public LayoutParams(MarginLayoutParams source) {
    486             super(source);
    487         }
    488 
    489         @Override
    490         protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
    491             // We don't want to force users to specify a layout_width
    492             if (a.hasValue(widthAttr)) {
    493                 width = a.getLayoutDimension(widthAttr, "layout_width");
    494             } else {
    495                 width = MATCH_PARENT;
    496             }
    497 
    498             // We don't want to force users to specify a layout_height
    499             if (a.hasValue(heightAttr)) {
    500                 height = a.getLayoutDimension(heightAttr, "layout_height");
    501             } else {
    502                 height = WRAP_CONTENT;
    503             }
    504         }
    505     }
    506 
    507     // special transparent hierarchy change listener
    508     private class ChildrenTracker implements OnHierarchyChangeListener {
    509         private OnHierarchyChangeListener listener;
    510 
    511         private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
    512             this.listener = listener;
    513         }
    514 
    515         public void onChildViewAdded(View parent, View child) {
    516             // dirties the index to column map
    517             mColumnToChildIndex = null;
    518 
    519             if (this.listener != null) {
    520                 this.listener.onChildViewAdded(parent, child);
    521             }
    522         }
    523 
    524         public void onChildViewRemoved(View parent, View child) {
    525             // dirties the index to column map
    526             mColumnToChildIndex = null;
    527 
    528             if (this.listener != null) {
    529                 this.listener.onChildViewRemoved(parent, child);
    530             }
    531         }
    532     }
    533 }
    534