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