blob: 5628cab9d31329445547d03b51ff904e764dc0d2 [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;
25import android.view.ViewGroup;
26import android.view.ViewDebug;
27
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
38 * {@link android.widget.TableLayout.LayoutParams#FILL_PARENT} and
39 * {@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
121 layoutHorizontal();
122 }
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
227 switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
228 case Gravity.LEFT:
229 // don't offset on X axis
230 break;
231 case Gravity.RIGHT:
232 lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT];
233 break;
234 case Gravity.CENTER_HORIZONTAL:
235 lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2;
236 break;
237 }
238 } else {
239 lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0;
240 }
241 } else {
242 // fail silently when column widths are not available
243 super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec,
244 totalWidth, heightMeasureSpec, totalHeight);
245 }
246 }
247
248 /**
249 * {@inheritDoc}
250 */
251 @Override
252 int getChildrenSkipCount(View child, int index) {
253 LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
254
255 // when the span is 1 (default), we need to skip 0 child
256 return layoutParams.span - 1;
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 @Override
263 int getLocationOffset(View child) {
264 return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 @Override
271 int getNextLocationOffset(View child) {
272 return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
273 }
274
275 /**
276 * <p>Measures the preferred width of each child, including its margins.</p>
277 *
278 * @param widthMeasureSpec the width constraint imposed by our parent
279 *
280 * @return an array of integers corresponding to the width of each cell, or
281 * column, in this row
282 * {@hide}
283 */
284 int[] getColumnsWidths(int widthMeasureSpec) {
285 final int numColumns = getVirtualChildCount();
286 if (mColumnWidths == null || numColumns != mColumnWidths.length) {
287 mColumnWidths = new int[numColumns];
288 }
289
290 final int[] columnWidths = mColumnWidths;
291
292 for (int i = 0; i < numColumns; i++) {
293 final View child = getVirtualChildAt(i);
294 if (child != null && child.getVisibility() != GONE) {
295 final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
296 if (layoutParams.span == 1) {
297 int spec;
298 switch (layoutParams.width) {
299 case LayoutParams.WRAP_CONTENT:
300 spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
301 break;
302 case LayoutParams.FILL_PARENT:
303 spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
304 break;
305 default:
306 spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
307 }
308 child.measure(spec, spec);
309
310 final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
311 layoutParams.rightMargin;
312 columnWidths[i] = width;
313 } else {
314 columnWidths[i] = 0;
315 }
316 } else {
317 columnWidths[i] = 0;
318 }
319 }
320
321 return columnWidths;
322 }
323
324 /**
325 * <p>Sets the width of all of the columns in this row. At layout time,
326 * this row sets a fixed width, as defined by <code>columnWidths</code>,
327 * on each child (or cell, or column.)</p>
328 *
329 * @param columnWidths the fixed width of each column that this row must
330 * honor
331 * @throws IllegalArgumentException when columnWidths' length is smaller
332 * than the number of children in this row
333 * {@hide}
334 */
335 void setColumnsWidthConstraints(int[] columnWidths) {
336 if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
337 throw new IllegalArgumentException(
338 "columnWidths should be >= getVirtualChildCount()");
339 }
340
341 mConstrainedColumnWidths = columnWidths;
342 }
343
344 /**
345 * {@inheritDoc}
346 */
347 @Override
348 public LayoutParams generateLayoutParams(AttributeSet attrs) {
349 return new TableRow.LayoutParams(getContext(), attrs);
350 }
351
352 /**
353 * Returns a set of layout parameters with a width of
354 * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
355 * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
356 */
357 @Override
358 protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
359 return new LayoutParams();
360 }
361
362 /**
363 * {@inheritDoc}
364 */
365 @Override
366 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
367 return p instanceof TableRow.LayoutParams;
368 }
369
370 /**
371 * {@inheritDoc}
372 */
373 @Override
374 protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
375 return new LayoutParams(p);
376 }
377
378 /**
379 * <p>Set of layout parameters used in table rows.</p>
380 *
381 * @see android.widget.TableLayout.LayoutParams
382 *
383 * @attr ref android.R.styleable#TableRow_Cell_layout_column
384 * @attr ref android.R.styleable#TableRow_Cell_layout_span
385 */
386 public static class LayoutParams extends LinearLayout.LayoutParams {
387 /**
388 * <p>The column index of the cell represented by the widget.</p>
389 */
390 @ViewDebug.ExportedProperty
391 public int column;
392
393 /**
394 * <p>The number of columns the widgets spans over.</p>
395 */
396 @ViewDebug.ExportedProperty
397 public int span;
398
399 private static final int LOCATION = 0;
400 private static final int LOCATION_NEXT = 1;
401
402 private int[] mOffset = new int[2];
403
404 /**
405 * {@inheritDoc}
406 */
407 public LayoutParams(Context c, AttributeSet attrs) {
408 super(c, attrs);
409
410 TypedArray a =
411 c.obtainStyledAttributes(attrs,
412 com.android.internal.R.styleable.TableRow_Cell);
413
414 column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
415 span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
416 if (span <= 1) {
417 span = 1;
418 }
419
420 a.recycle();
421 }
422
423 /**
424 * <p>Sets the child width and the child height.</p>
425 *
426 * @param w the desired width
427 * @param h the desired height
428 */
429 public LayoutParams(int w, int h) {
430 super(w, h);
431 column = -1;
432 span = 1;
433 }
434
435 /**
436 * <p>Sets the child width, height and weight.</p>
437 *
438 * @param w the desired width
439 * @param h the desired height
440 * @param initWeight the desired weight
441 */
442 public LayoutParams(int w, int h, float initWeight) {
443 super(w, h, initWeight);
444 column = -1;
445 span = 1;
446 }
447
448 /**
449 * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
450 * and the child height to
451 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
452 */
453 public LayoutParams() {
454 super(FILL_PARENT, WRAP_CONTENT);
455 column = -1;
456 span = 1;
457 }
458
459 /**
460 * <p>Puts the view in the specified column.</p>
461 *
462 * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
463 * and the child height to
464 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
465 *
466 * @param column the column index for the view
467 */
468 public LayoutParams(int column) {
469 this();
470 this.column = column;
471 }
472
473 /**
474 * {@inheritDoc}
475 */
476 public LayoutParams(ViewGroup.LayoutParams p) {
477 super(p);
478 }
479
480 /**
481 * {@inheritDoc}
482 */
483 public LayoutParams(MarginLayoutParams source) {
484 super(source);
485 }
486
487 @Override
488 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
489 // We don't want to force users to specify a layout_width
490 if (a.hasValue(widthAttr)) {
491 width = a.getLayoutDimension(widthAttr, "layout_width");
492 } else {
493 width = FILL_PARENT;
494 }
495
496 // We don't want to force users to specify a layout_height
497 if (a.hasValue(heightAttr)) {
498 height = a.getLayoutDimension(heightAttr, "layout_height");
499 } else {
500 height = WRAP_CONTENT;
501 }
502 }
503 }
504
505 // special transparent hierarchy change listener
506 private class ChildrenTracker implements OnHierarchyChangeListener {
507 private OnHierarchyChangeListener listener;
508
509 private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
510 this.listener = listener;
511 }
512
513 public void onChildViewAdded(View parent, View child) {
514 // dirties the index to column map
515 mColumnToChildIndex = null;
516
517 if (this.listener != null) {
518 this.listener.onChildViewAdded(parent, child);
519 }
520 }
521
522 public void onChildViewRemoved(View parent, View child) {
523 // dirties the index to column map
524 mColumnToChildIndex = null;
525
526 if (this.listener != null) {
527 this.listener.onChildViewRemoved(parent, child);
528 }
529 }
530 }
531}