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                 switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    228                     case Gravity.LEFT:
    229                         // don't offset on X axis
    230                         break;
    231                     case Gravity.RIGHT:
    232                         lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT];
    233                         break;
    234                     case Gravity.CENTER_HORIZONTAL:
    235                         lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2;
    236                         break;
    237                 }
    238             } else {
    239                 lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0;
    240             }
    241         } else {
    242             // fail silently when column widths are not available
    243             super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec,
    244                     totalWidth, heightMeasureSpec, totalHeight);
    245         }
    246     }
    247 
    248     /**
    249      * {@inheritDoc}
    250      */
    251     @Override
    252     int getChildrenSkipCount(View child, int index) {
    253         LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
    254 
    255         // when the span is 1 (default), we need to skip 0 child
    256         return layoutParams.span - 1;
    257     }
    258 
    259     /**
    260      * {@inheritDoc}
    261      */
    262     @Override
    263     int getLocationOffset(View child) {
    264         return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
    265     }
    266 
    267     /**
    268      * {@inheritDoc}
    269      */
    270     @Override
    271     int getNextLocationOffset(View child) {
    272         return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
    273     }
    274 
    275     /**
    276      * <p>Measures the preferred width of each child, including its margins.</p>
    277      *
    278      * @param widthMeasureSpec the width constraint imposed by our parent
    279      *
    280      * @return an array of integers corresponding to the width of each cell, or
    281      *         column, in this row
    282      * {@hide}
    283      */
    284     int[] getColumnsWidths(int widthMeasureSpec) {
    285         final int numColumns = getVirtualChildCount();
    286         if (mColumnWidths == null || numColumns != mColumnWidths.length) {
    287             mColumnWidths = new int[numColumns];
    288         }
    289 
    290         final int[] columnWidths = mColumnWidths;
    291 
    292         for (int i = 0; i < numColumns; i++) {
    293             final View child = getVirtualChildAt(i);
    294             if (child != null && child.getVisibility() != GONE) {
    295                 final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
    296                 if (layoutParams.span == 1) {
    297                     int spec;
    298                     switch (layoutParams.width) {
    299                         case LayoutParams.WRAP_CONTENT:
    300                             spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
    301                             break;
    302                         case LayoutParams.MATCH_PARENT:
    303                             spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    304                             break;
    305                         default:
    306                             spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
    307                     }
    308                     child.measure(spec, spec);
    309 
    310                     final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
    311                             layoutParams.rightMargin;
    312                     columnWidths[i] = width;
    313                 } else {
    314                     columnWidths[i] = 0;
    315                 }
    316             } else {
    317                 columnWidths[i] = 0;
    318             }
    319         }
    320 
    321         return columnWidths;
    322     }
    323 
    324     /**
    325      * <p>Sets the width of all of the columns in this row. At layout time,
    326      * this row sets a fixed width, as defined by <code>columnWidths</code>,
    327      * on each child (or cell, or column.)</p>
    328      *
    329      * @param columnWidths the fixed width of each column that this row must
    330      *                     honor
    331      * @throws IllegalArgumentException when columnWidths' length is smaller
    332      *         than the number of children in this row
    333      * {@hide}
    334      */
    335     void setColumnsWidthConstraints(int[] columnWidths) {
    336         if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
    337             throw new IllegalArgumentException(
    338                     "columnWidths should be >= getVirtualChildCount()");
    339         }
    340 
    341         mConstrainedColumnWidths = columnWidths;
    342     }
    343 
    344     /**
    345      * {@inheritDoc}
    346      */
    347     @Override
    348     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    349         return new TableRow.LayoutParams(getContext(), attrs);
    350     }
    351 
    352     /**
    353      * Returns a set of layout parameters with a width of
    354      * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
    355      * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
    356      */
    357     @Override
    358     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
    359         return new LayoutParams();
    360     }
    361 
    362     /**
    363      * {@inheritDoc}
    364      */
    365     @Override
    366     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    367         return p instanceof TableRow.LayoutParams;
    368     }
    369 
    370     /**
    371      * {@inheritDoc}
    372      */
    373     @Override
    374     protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    375         return new LayoutParams(p);
    376     }
    377 
    378     /**
    379      * <p>Set of layout parameters used in table rows.</p>
    380      *
    381      * @see android.widget.TableLayout.LayoutParams
    382      *
    383      * @attr ref android.R.styleable#TableRow_Cell_layout_column
    384      * @attr ref android.R.styleable#TableRow_Cell_layout_span
    385      */
    386     public static class LayoutParams extends LinearLayout.LayoutParams {
    387         /**
    388          * <p>The column index of the cell represented by the widget.</p>
    389          */
    390         @ViewDebug.ExportedProperty(category = "layout")
    391         public int column;
    392 
    393         /**
    394          * <p>The number of columns the widgets spans over.</p>
    395          */
    396         @ViewDebug.ExportedProperty(category = "layout")
    397         public int span;
    398 
    399         private static final int LOCATION = 0;
    400         private static final int LOCATION_NEXT = 1;
    401 
    402         private int[] mOffset = new int[2];
    403 
    404         /**
    405          * {@inheritDoc}
    406          */
    407         public LayoutParams(Context c, AttributeSet attrs) {
    408             super(c, attrs);
    409 
    410             TypedArray a =
    411                     c.obtainStyledAttributes(attrs,
    412                             com.android.internal.R.styleable.TableRow_Cell);
    413 
    414             column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
    415             span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
    416             if (span <= 1) {
    417                 span = 1;
    418             }
    419 
    420             a.recycle();
    421         }
    422 
    423         /**
    424          * <p>Sets the child width and the child height.</p>
    425          *
    426          * @param w the desired width
    427          * @param h the desired height
    428          */
    429         public LayoutParams(int w, int h) {
    430             super(w, h);
    431             column = -1;
    432             span = 1;
    433         }
    434 
    435         /**
    436          * <p>Sets the child width, height and weight.</p>
    437          *
    438          * @param w the desired width
    439          * @param h the desired height
    440          * @param initWeight the desired weight
    441          */
    442         public LayoutParams(int w, int h, float initWeight) {
    443             super(w, h, initWeight);
    444             column = -1;
    445             span = 1;
    446         }
    447 
    448         /**
    449          * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
    450          * and the child height to
    451          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
    452          */
    453         public LayoutParams() {
    454             super(MATCH_PARENT, WRAP_CONTENT);
    455             column = -1;
    456             span = 1;
    457         }
    458 
    459         /**
    460          * <p>Puts the view in the specified column.</p>
    461          *
    462          * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
    463          * and the child height to
    464          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
    465          *
    466          * @param column the column index for the view
    467          */
    468         public LayoutParams(int column) {
    469             this();
    470             this.column = column;
    471         }
    472 
    473         /**
    474          * {@inheritDoc}
    475          */
    476         public LayoutParams(ViewGroup.LayoutParams p) {
    477             super(p);
    478         }
    479 
    480         /**
    481          * {@inheritDoc}
    482          */
    483         public LayoutParams(MarginLayoutParams source) {
    484             super(source);
    485         }
    486 
    487         @Override
    488         protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
    489             // We don't want to force users to specify a layout_width
    490             if (a.hasValue(widthAttr)) {
    491                 width = a.getLayoutDimension(widthAttr, "layout_width");
    492             } else {
    493                 width = MATCH_PARENT;
    494             }
    495 
    496             // We don't want to force users to specify a layout_height
    497             if (a.hasValue(heightAttr)) {
    498                 height = a.getLayoutDimension(heightAttr, "layout_height");
    499             } else {
    500                 height = WRAP_CONTENT;
    501             }
    502         }
    503     }
    504 
    505     // special transparent hierarchy change listener
    506     private class ChildrenTracker implements OnHierarchyChangeListener {
    507         private OnHierarchyChangeListener listener;
    508 
    509         private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
    510             this.listener = listener;
    511         }
    512 
    513         public void onChildViewAdded(View parent, View child) {
    514             // dirties the index to column map
    515             mColumnToChildIndex = null;
    516 
    517             if (this.listener != null) {
    518                 this.listener.onChildViewAdded(parent, child);
    519             }
    520         }
    521 
    522         public void onChildViewRemoved(View parent, View child) {
    523             // dirties the index to column map
    524             mColumnToChildIndex = null;
    525 
    526             if (this.listener != null) {
    527                 this.listener.onChildViewRemoved(parent, child);
    528             }
    529         }
    530     }
    531 }
    532