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.annotation.NonNull;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.util.AttributeSet;
     23 import android.util.SparseIntArray;
     24 import android.view.Gravity;
     25 import android.view.View;
     26 import android.view.ViewDebug;
     27 import android.view.ViewGroup;
     28 import android.view.ViewHierarchyEncoder;
     29 import android.view.inspector.InspectableProperty;
     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         final 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, int heightMeasureSpec) {
    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.makeSafeMeasureSpec(
    308                                     MeasureSpec.getSize(heightMeasureSpec),
    309                                     MeasureSpec.UNSPECIFIED);
    310                             break;
    311                         default:
    312                             spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
    313                     }
    314                     child.measure(spec, spec);
    315 
    316                     final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
    317                             layoutParams.rightMargin;
    318                     columnWidths[i] = width;
    319                 } else {
    320                     columnWidths[i] = 0;
    321                 }
    322             } else {
    323                 columnWidths[i] = 0;
    324             }
    325         }
    326 
    327         return columnWidths;
    328     }
    329 
    330     /**
    331      * <p>Sets the width of all of the columns in this row. At layout time,
    332      * this row sets a fixed width, as defined by <code>columnWidths</code>,
    333      * on each child (or cell, or column.)</p>
    334      *
    335      * @param columnWidths the fixed width of each column that this row must
    336      *                     honor
    337      * @throws IllegalArgumentException when columnWidths' length is smaller
    338      *         than the number of children in this row
    339      * {@hide}
    340      */
    341     void setColumnsWidthConstraints(int[] columnWidths) {
    342         if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
    343             throw new IllegalArgumentException(
    344                     "columnWidths should be >= getVirtualChildCount()");
    345         }
    346 
    347         mConstrainedColumnWidths = columnWidths;
    348     }
    349 
    350     /**
    351      * {@inheritDoc}
    352      */
    353     @Override
    354     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    355         return new TableRow.LayoutParams(getContext(), attrs);
    356     }
    357 
    358     /**
    359      * Returns a set of layout parameters with a width of
    360      * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
    361      * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
    362      */
    363     @Override
    364     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
    365         return new LayoutParams();
    366     }
    367 
    368     /**
    369      * {@inheritDoc}
    370      */
    371     @Override
    372     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    373         return p instanceof TableRow.LayoutParams;
    374     }
    375 
    376     /**
    377      * {@inheritDoc}
    378      */
    379     @Override
    380     protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    381         return new LayoutParams(p);
    382     }
    383 
    384     @Override
    385     public CharSequence getAccessibilityClassName() {
    386         return TableRow.class.getName();
    387     }
    388 
    389     /**
    390      * <p>Set of layout parameters used in table rows.</p>
    391      *
    392      * @see android.widget.TableLayout.LayoutParams
    393      *
    394      * @attr ref android.R.styleable#TableRow_Cell_layout_column
    395      * @attr ref android.R.styleable#TableRow_Cell_layout_span
    396      */
    397     public static class LayoutParams extends LinearLayout.LayoutParams {
    398         /**
    399          * <p>The column index of the cell represented by the widget.</p>
    400          */
    401         @ViewDebug.ExportedProperty(category = "layout")
    402         @InspectableProperty(name = "layout_column")
    403         public int column;
    404 
    405         /**
    406          * <p>The number of columns the widgets spans over.</p>
    407          */
    408         @ViewDebug.ExportedProperty(category = "layout")
    409         @InspectableProperty(name = "layout_span")
    410         public int span;
    411 
    412         private static final int LOCATION = 0;
    413         private static final int LOCATION_NEXT = 1;
    414 
    415         private int[] mOffset = new int[2];
    416 
    417         /**
    418          * {@inheritDoc}
    419          */
    420         public LayoutParams(Context c, AttributeSet attrs) {
    421             super(c, attrs);
    422 
    423             TypedArray a =
    424                     c.obtainStyledAttributes(attrs,
    425                             com.android.internal.R.styleable.TableRow_Cell);
    426 
    427             column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
    428             span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
    429             if (span <= 1) {
    430                 span = 1;
    431             }
    432 
    433             a.recycle();
    434         }
    435 
    436         /**
    437          * <p>Sets the child width and the child height.</p>
    438          *
    439          * @param w the desired width
    440          * @param h the desired height
    441          */
    442         public LayoutParams(int w, int h) {
    443             super(w, h);
    444             column = -1;
    445             span = 1;
    446         }
    447 
    448         /**
    449          * <p>Sets the child width, height and weight.</p>
    450          *
    451          * @param w the desired width
    452          * @param h the desired height
    453          * @param initWeight the desired weight
    454          */
    455         public LayoutParams(int w, int h, float initWeight) {
    456             super(w, h, initWeight);
    457             column = -1;
    458             span = 1;
    459         }
    460 
    461         /**
    462          * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
    463          * and the child height to
    464          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
    465          */
    466         public LayoutParams() {
    467             super(MATCH_PARENT, WRAP_CONTENT);
    468             column = -1;
    469             span = 1;
    470         }
    471 
    472         /**
    473          * <p>Puts the view in the specified column.</p>
    474          *
    475          * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
    476          * and the child height to
    477          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
    478          *
    479          * @param column the column index for the view
    480          */
    481         public LayoutParams(int column) {
    482             this();
    483             this.column = column;
    484         }
    485 
    486         /**
    487          * {@inheritDoc}
    488          */
    489         public LayoutParams(ViewGroup.LayoutParams p) {
    490             super(p);
    491         }
    492 
    493         /**
    494          * {@inheritDoc}
    495          */
    496         public LayoutParams(MarginLayoutParams source) {
    497             super(source);
    498         }
    499 
    500         @Override
    501         protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
    502             // We don't want to force users to specify a layout_width
    503             if (a.hasValue(widthAttr)) {
    504                 width = a.getLayoutDimension(widthAttr, "layout_width");
    505             } else {
    506                 width = MATCH_PARENT;
    507             }
    508 
    509             // We don't want to force users to specify a layout_height
    510             if (a.hasValue(heightAttr)) {
    511                 height = a.getLayoutDimension(heightAttr, "layout_height");
    512             } else {
    513                 height = WRAP_CONTENT;
    514             }
    515         }
    516 
    517         /** @hide */
    518         @Override
    519         protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
    520             super.encodeProperties(encoder);
    521             encoder.addProperty("layout:column", column);
    522             encoder.addProperty("layout:span", span);
    523         }
    524     }
    525 
    526     // special transparent hierarchy change listener
    527     private class ChildrenTracker implements OnHierarchyChangeListener {
    528         private OnHierarchyChangeListener listener;
    529 
    530         private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
    531             this.listener = listener;
    532         }
    533 
    534         public void onChildViewAdded(View parent, View child) {
    535             // dirties the index to column map
    536             mColumnToChildIndex = null;
    537 
    538             if (this.listener != null) {
    539                 this.listener.onChildViewAdded(parent, child);
    540             }
    541         }
    542 
    543         public void onChildViewRemoved(View parent, View child) {
    544             // dirties the index to column map
    545             mColumnToChildIndex = null;
    546 
    547             if (this.listener != null) {
    548                 this.listener.onChildViewRemoved(parent, child);
    549             }
    550         }
    551     }
    552 }
    553