• 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.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      */
TableRow(Context context)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      */
TableRow(Context context, AttributeSet attrs)72     public TableRow(Context context, AttributeSet attrs) {
73         super(context, attrs);
74         initTableRow();
75     }
76 
initTableRow()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
setOnHierarchyChangeListener(OnHierarchyChangeListener listener)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      */
setColumnCollapsed(int columnIndex, boolean collapsed)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)112     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
113         // enforce horizontal layout
114         measureHorizontal(widthMeasureSpec, heightMeasureSpec);
115     }
116 
117     /**
118      * {@inheritDoc}
119      */
120     @Override
onLayout(boolean changed, int l, int t, int r, int b)121     protected void onLayout(boolean changed, int l, int t, int r, int b) {
122         // enforce horizontal layout
123         layoutHorizontal();
124     }
125 
126     /**
127      * {@inheritDoc}
128      */
129     @Override
getVirtualChildAt(int i)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
getVirtualChildCount()147     public int getVirtualChildCount() {
148         if (mColumnToChildIndex == null) {
149             mapIndexAndColumns();
150         }
151         return mNumColumns;
152     }
153 
mapIndexAndColumns()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
measureNullChild(int childIndex)183     int measureNullChild(int childIndex) {
184         return mConstrainedColumnWidths[childIndex];
185     }
186 
187     /**
188      * {@inheritDoc}
189      */
190     @Override
measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)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
getChildrenSkipCount(View child, int index)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
getLocationOffset(View child)267     int getLocationOffset(View child) {
268         return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
269     }
270 
271     /**
272      * {@inheritDoc}
273      */
274     @Override
getNextLocationOffset(View child)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      */
getColumnsWidths(int widthMeasureSpec)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      */
setColumnsWidthConstraints(int[] columnWidths)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
generateLayoutParams(AttributeSet attrs)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
generateDefaultLayoutParams()362     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
363         return new LayoutParams();
364     }
365 
366     /**
367      * {@inheritDoc}
368      */
369     @Override
checkLayoutParams(ViewGroup.LayoutParams p)370     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
371         return p instanceof TableRow.LayoutParams;
372     }
373 
374     /**
375      * {@inheritDoc}
376      */
377     @Override
generateLayoutParams(ViewGroup.LayoutParams p)378     protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
379         return new LayoutParams(p);
380     }
381 
382     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)383     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
384         super.onInitializeAccessibilityEvent(event);
385         event.setClassName(TableRow.class.getName());
386     }
387 
388     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)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          */
LayoutParams(Context c, AttributeSet attrs)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          */
LayoutParams(int w, int h)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          */
LayoutParams(int w, int h, float initWeight)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          */
LayoutParams()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          */
LayoutParams(int column)484         public LayoutParams(int column) {
485             this();
486             this.column = column;
487         }
488 
489         /**
490          * {@inheritDoc}
491          */
LayoutParams(ViewGroup.LayoutParams p)492         public LayoutParams(ViewGroup.LayoutParams p) {
493             super(p);
494         }
495 
496         /**
497          * {@inheritDoc}
498          */
LayoutParams(MarginLayoutParams source)499         public LayoutParams(MarginLayoutParams source) {
500             super(source);
501         }
502 
503         @Override
setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)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 
setOnHierarchyChangeListener(OnHierarchyChangeListener listener)525         private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
526             this.listener = listener;
527         }
528 
onChildViewAdded(View parent, View child)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 
onChildViewRemoved(View parent, View child)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