blob: ac23093a007358c71c356082f14a2f70dc4dab6c [file] [log] [blame]
Rahul Ravikumar05336002019-10-14 15:04:32 -07001/*
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.annotation.NonNull;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.util.AttributeSet;
23import android.util.SparseIntArray;
24import android.view.Gravity;
25import android.view.View;
26import android.view.ViewDebug;
27import android.view.ViewGroup;
28import android.view.ViewHierarchyEncoder;
29import android.view.inspector.InspectableProperty;
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 */
47public 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 */
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 */
72 public TableRow(Context context, AttributeSet attrs) {
73 super(context, attrs);
74 initTableRow();
75 }
76
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
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 */
101 void setColumnCollapsed(int columnIndex, boolean collapsed) {
102 final View child = getVirtualChildAt(columnIndex);
103 if (child != null) {
104 child.setVisibility(collapsed ? GONE : VISIBLE);
105 }
106 }
107
108 /**
109 * {@inheritDoc}
110 */
111 @Override
112 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
113 // enforce horizontal layout
114 measureHorizontal(widthMeasureSpec, heightMeasureSpec);
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 @Override
121 protected void onLayout(boolean changed, int l, int t, int r, int b) {
122 // enforce horizontal layout
123 layoutHorizontal(l, t, r, b);
124 }
125
126 /**
127 * {@inheritDoc}
128 */
129 @Override
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
147 public int getVirtualChildCount() {
148 if (mColumnToChildIndex == null) {
149 mapIndexAndColumns();
150 }
151 return mNumColumns;
152 }
153
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
183 int measureNullChild(int childIndex) {
184 return mConstrainedColumnWidths[childIndex];
185 }
186
187 /**
188 * {@inheritDoc}
189 */
190 @Override
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
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
267 int getLocationOffset(View child) {
268 return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
269 }
270
271 /**
272 * {@inheritDoc}
273 */
274 @Override
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 */
288 int[] getColumnsWidths(int widthMeasureSpec, int heightMeasureSpec) {
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.makeSafeMeasureSpec(
308 MeasureSpec.getSize(heightMeasureSpec),
309 MeasureSpec.UNSPECIFIED);
310 break;
311 default:
312 spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
313 }
314 child.measure(spec, spec);
315
316 final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
317 layoutParams.rightMargin;
318 columnWidths[i] = width;
319 } else {
320 columnWidths[i] = 0;
321 }
322 } else {
323 columnWidths[i] = 0;
324 }
325 }
326
327 return columnWidths;
328 }
329
330 /**
331 * <p>Sets the width of all of the columns in this row. At layout time,
332 * this row sets a fixed width, as defined by <code>columnWidths</code>,
333 * on each child (or cell, or column.)</p>
334 *
335 * @param columnWidths the fixed width of each column that this row must
336 * honor
337 * @throws IllegalArgumentException when columnWidths' length is smaller
338 * than the number of children in this row
339 * {@hide}
340 */
341 void setColumnsWidthConstraints(int[] columnWidths) {
342 if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
343 throw new IllegalArgumentException(
344 "columnWidths should be >= getVirtualChildCount()");
345 }
346
347 mConstrainedColumnWidths = columnWidths;
348 }
349
350 /**
351 * {@inheritDoc}
352 */
353 @Override
354 public LayoutParams generateLayoutParams(AttributeSet attrs) {
355 return new TableRow.LayoutParams(getContext(), attrs);
356 }
357
358 /**
359 * Returns a set of layout parameters with a width of
360 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
361 * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
362 */
363 @Override
364 protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
365 return new LayoutParams();
366 }
367
368 /**
369 * {@inheritDoc}
370 */
371 @Override
372 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
373 return p instanceof TableRow.LayoutParams;
374 }
375
376 /**
377 * {@inheritDoc}
378 */
379 @Override
380 protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
381 return new LayoutParams(p);
382 }
383
384 @Override
385 public CharSequence getAccessibilityClassName() {
386 return TableRow.class.getName();
387 }
388
389 /**
390 * <p>Set of layout parameters used in table rows.</p>
391 *
392 * @see android.widget.TableLayout.LayoutParams
393 *
394 * @attr ref android.R.styleable#TableRow_Cell_layout_column
395 * @attr ref android.R.styleable#TableRow_Cell_layout_span
396 */
397 public static class LayoutParams extends LinearLayout.LayoutParams {
398 /**
399 * <p>The column index of the cell represented by the widget.</p>
400 */
401 @ViewDebug.ExportedProperty(category = "layout")
402 @InspectableProperty(name = "layout_column")
403 public int column;
404
405 /**
406 * <p>The number of columns the widgets spans over.</p>
407 */
408 @ViewDebug.ExportedProperty(category = "layout")
409 @InspectableProperty(name = "layout_span")
410 public int span;
411
412 private static final int LOCATION = 0;
413 private static final int LOCATION_NEXT = 1;
414
415 private int[] mOffset = new int[2];
416
417 /**
418 * {@inheritDoc}
419 */
420 public LayoutParams(Context c, AttributeSet attrs) {
421 super(c, attrs);
422
423 TypedArray a =
424 c.obtainStyledAttributes(attrs,
425 com.android.internal.R.styleable.TableRow_Cell);
426
427 column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
428 span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
429 if (span <= 1) {
430 span = 1;
431 }
432
433 a.recycle();
434 }
435
436 /**
437 * <p>Sets the child width and the child height.</p>
438 *
439 * @param w the desired width
440 * @param h the desired height
441 */
442 public LayoutParams(int w, int h) {
443 super(w, h);
444 column = -1;
445 span = 1;
446 }
447
448 /**
449 * <p>Sets the child width, height and weight.</p>
450 *
451 * @param w the desired width
452 * @param h the desired height
453 * @param initWeight the desired weight
454 */
455 public LayoutParams(int w, int h, float initWeight) {
456 super(w, h, initWeight);
457 column = -1;
458 span = 1;
459 }
460
461 /**
462 * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
463 * and the child height to
464 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
465 */
466 public LayoutParams() {
467 super(MATCH_PARENT, WRAP_CONTENT);
468 column = -1;
469 span = 1;
470 }
471
472 /**
473 * <p>Puts the view in the specified column.</p>
474 *
475 * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
476 * and the child height to
477 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
478 *
479 * @param column the column index for the view
480 */
481 public LayoutParams(int column) {
482 this();
483 this.column = column;
484 }
485
486 /**
487 * {@inheritDoc}
488 */
489 public LayoutParams(ViewGroup.LayoutParams p) {
490 super(p);
491 }
492
493 /**
494 * {@inheritDoc}
495 */
496 public LayoutParams(MarginLayoutParams source) {
497 super(source);
498 }
499
500 @Override
501 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
502 // We don't want to force users to specify a layout_width
503 if (a.hasValue(widthAttr)) {
504 width = a.getLayoutDimension(widthAttr, "layout_width");
505 } else {
506 width = MATCH_PARENT;
507 }
508
509 // We don't want to force users to specify a layout_height
510 if (a.hasValue(heightAttr)) {
511 height = a.getLayoutDimension(heightAttr, "layout_height");
512 } else {
513 height = WRAP_CONTENT;
514 }
515 }
516
517 /** @hide */
518 @Override
519 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
520 super.encodeProperties(encoder);
521 encoder.addProperty("layout:column", column);
522 encoder.addProperty("layout:span", span);
523 }
524 }
525
526 // special transparent hierarchy change listener
527 private class ChildrenTracker implements OnHierarchyChangeListener {
528 private OnHierarchyChangeListener listener;
529
530 private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
531 this.listener = listener;
532 }
533
534 public void onChildViewAdded(View parent, View child) {
535 // dirties the index to column map
536 mColumnToChildIndex = null;
537
538 if (this.listener != null) {
539 this.listener.onChildViewAdded(parent, child);
540 }
541 }
542
543 public void onChildViewRemoved(View parent, View child) {
544 // dirties the index to column map
545 mColumnToChildIndex = null;
546
547 if (this.listener != null) {
548 this.listener.onChildViewRemoved(parent, child);
549 }
550 }
551 }
552}