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