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 import 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 */ 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 final 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(l, t, r, b); 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, int heightMeasureSpec)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 */ setColumnsWidthConstraints(int[] columnWidths)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 generateLayoutParams(AttributeSet attrs)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 generateDefaultLayoutParams()364 protected LinearLayout.LayoutParams generateDefaultLayoutParams() { 365 return new LayoutParams(); 366 } 367 368 /** 369 * {@inheritDoc} 370 */ 371 @Override checkLayoutParams(ViewGroup.LayoutParams p)372 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 373 return p instanceof TableRow.LayoutParams; 374 } 375 376 /** 377 * {@inheritDoc} 378 */ 379 @Override generateLayoutParams(ViewGroup.LayoutParams p)380 protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 381 return new LayoutParams(p); 382 } 383 384 @Override getAccessibilityClassName()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 */ LayoutParams(Context c, AttributeSet attrs)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 */ LayoutParams(int w, int h)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 */ LayoutParams(int w, int h, float initWeight)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 */ LayoutParams()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 */ LayoutParams(int column)481 public LayoutParams(int column) { 482 this(); 483 this.column = column; 484 } 485 486 /** 487 * {@inheritDoc} 488 */ LayoutParams(ViewGroup.LayoutParams p)489 public LayoutParams(ViewGroup.LayoutParams p) { 490 super(p); 491 } 492 493 /** 494 * {@inheritDoc} 495 */ LayoutParams(MarginLayoutParams source)496 public LayoutParams(MarginLayoutParams source) { 497 super(source); 498 } 499 500 @Override setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)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 encodeProperties(@onNull ViewHierarchyEncoder encoder)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 setOnHierarchyChangeListener(OnHierarchyChangeListener listener)530 private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { 531 this.listener = listener; 532 } 533 onChildViewAdded(View parent, View child)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 onChildViewRemoved(View parent, View child)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 } 553