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