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