• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
TableRow(Context context)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      */
TableRow(Context context, AttributeSet attrs)71     public TableRow(Context context, AttributeSet attrs) {
72         super(context, attrs);
73         initTableRow();
74     }
75 
initTableRow()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
setOnHierarchyChangeListener(OnHierarchyChangeListener listener)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      */
setColumnCollapsed(int columnIndex, boolean collapsed)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)111     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
112         // enforce horizontal layout
113         measureHorizontal(widthMeasureSpec, heightMeasureSpec);
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     @Override
onLayout(boolean changed, int l, int t, int r, int b)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
getVirtualChildAt(int i)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
getVirtualChildCount()146     public int getVirtualChildCount() {
147         if (mColumnToChildIndex == null) {
148             mapIndexAndColumns();
149         }
150         return mNumColumns;
151     }
152 
mapIndexAndColumns()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
measureNullChild(int childIndex)182     int measureNullChild(int childIndex) {
183         return mConstrainedColumnWidths[childIndex];
184     }
185 
186     /**
187      * {@inheritDoc}
188      */
189     @Override
measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)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
getChildrenSkipCount(View child, int index)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
getLocationOffset(View child)266     int getLocationOffset(View child) {
267         return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
268     }
269 
270     /**
271      * {@inheritDoc}
272      */
273     @Override
getNextLocationOffset(View child)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      */
getColumnsWidths(int widthMeasureSpec, int heightMeasureSpec)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      */
setColumnsWidthConstraints(int[] columnWidths)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
generateLayoutParams(AttributeSet attrs)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
generateDefaultLayoutParams()363     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
364         return new LayoutParams();
365     }
366 
367     /**
368      * {@inheritDoc}
369      */
370     @Override
checkLayoutParams(ViewGroup.LayoutParams p)371     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
372         return p instanceof TableRow.LayoutParams;
373     }
374 
375     /**
376      * {@inheritDoc}
377      */
378     @Override
generateLayoutParams(ViewGroup.LayoutParams p)379     protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
380         return new LayoutParams(p);
381     }
382 
383     @Override
getAccessibilityClassName()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          */
LayoutParams(Context c, AttributeSet attrs)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          */
LayoutParams(int w, int h)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          */
LayoutParams(int w, int h, float initWeight)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          */
LayoutParams()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          */
LayoutParams(int column)478         public LayoutParams(int column) {
479             this();
480             this.column = column;
481         }
482 
483         /**
484          * {@inheritDoc}
485          */
LayoutParams(ViewGroup.LayoutParams p)486         public LayoutParams(ViewGroup.LayoutParams p) {
487             super(p);
488         }
489 
490         /**
491          * {@inheritDoc}
492          */
LayoutParams(MarginLayoutParams source)493         public LayoutParams(MarginLayoutParams source) {
494             super(source);
495         }
496 
497         @Override
setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)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
encodeProperties(@onNull ViewHierarchyEncoder encoder)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 
setOnHierarchyChangeListener(OnHierarchyChangeListener listener)527         private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
528             this.listener = listener;
529         }
530 
onChildViewAdded(View parent, View child)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 
onChildViewRemoved(View parent, View child)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