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.content.Context; 20 import android.content.res.TypedArray; 21 import android.util.AttributeSet; 22 import android.util.SparseIntArray; 23 import android.view.Gravity; 24 import android.view.View; 25 import android.view.ViewDebug; 26 import android.view.ViewGroup; 27 import android.view.accessibility.AccessibilityEvent; 28 import android.view.accessibility.AccessibilityNodeInfo; 29 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 */ 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 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) { 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.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 308 break; 309 default: 310 spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY); 311 } 312 child.measure(spec, spec); 313 314 final int width = child.getMeasuredWidth() + layoutParams.leftMargin + 315 layoutParams.rightMargin; 316 columnWidths[i] = width; 317 } else { 318 columnWidths[i] = 0; 319 } 320 } else { 321 columnWidths[i] = 0; 322 } 323 } 324 325 return columnWidths; 326 } 327 328 /** 329 * <p>Sets the width of all of the columns in this row. At layout time, 330 * this row sets a fixed width, as defined by <code>columnWidths</code>, 331 * on each child (or cell, or column.)</p> 332 * 333 * @param columnWidths the fixed width of each column that this row must 334 * honor 335 * @throws IllegalArgumentException when columnWidths' length is smaller 336 * than the number of children in this row 337 * {@hide} 338 */ 339 void setColumnsWidthConstraints(int[] columnWidths) { 340 if (columnWidths == null || columnWidths.length < getVirtualChildCount()) { 341 throw new IllegalArgumentException( 342 "columnWidths should be >= getVirtualChildCount()"); 343 } 344 345 mConstrainedColumnWidths = columnWidths; 346 } 347 348 /** 349 * {@inheritDoc} 350 */ 351 @Override 352 public LayoutParams generateLayoutParams(AttributeSet attrs) { 353 return new TableRow.LayoutParams(getContext(), attrs); 354 } 355 356 /** 357 * Returns a set of layout parameters with a width of 358 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, 359 * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning. 360 */ 361 @Override 362 protected LinearLayout.LayoutParams generateDefaultLayoutParams() { 363 return new LayoutParams(); 364 } 365 366 /** 367 * {@inheritDoc} 368 */ 369 @Override 370 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 371 return p instanceof TableRow.LayoutParams; 372 } 373 374 /** 375 * {@inheritDoc} 376 */ 377 @Override 378 protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 379 return new LayoutParams(p); 380 } 381 382 @Override 383 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 384 super.onInitializeAccessibilityEvent(event); 385 event.setClassName(TableRow.class.getName()); 386 } 387 388 @Override 389 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 390 super.onInitializeAccessibilityNodeInfo(info); 391 info.setClassName(TableRow.class.getName()); 392 } 393 394 /** 395 * <p>Set of layout parameters used in table rows.</p> 396 * 397 * @see android.widget.TableLayout.LayoutParams 398 * 399 * @attr ref android.R.styleable#TableRow_Cell_layout_column 400 * @attr ref android.R.styleable#TableRow_Cell_layout_span 401 */ 402 public static class LayoutParams extends LinearLayout.LayoutParams { 403 /** 404 * <p>The column index of the cell represented by the widget.</p> 405 */ 406 @ViewDebug.ExportedProperty(category = "layout") 407 public int column; 408 409 /** 410 * <p>The number of columns the widgets spans over.</p> 411 */ 412 @ViewDebug.ExportedProperty(category = "layout") 413 public int span; 414 415 private static final int LOCATION = 0; 416 private static final int LOCATION_NEXT = 1; 417 418 private int[] mOffset = new int[2]; 419 420 /** 421 * {@inheritDoc} 422 */ 423 public LayoutParams(Context c, AttributeSet attrs) { 424 super(c, attrs); 425 426 TypedArray a = 427 c.obtainStyledAttributes(attrs, 428 com.android.internal.R.styleable.TableRow_Cell); 429 430 column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1); 431 span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1); 432 if (span <= 1) { 433 span = 1; 434 } 435 436 a.recycle(); 437 } 438 439 /** 440 * <p>Sets the child width and the child height.</p> 441 * 442 * @param w the desired width 443 * @param h the desired height 444 */ 445 public LayoutParams(int w, int h) { 446 super(w, h); 447 column = -1; 448 span = 1; 449 } 450 451 /** 452 * <p>Sets the child width, height and weight.</p> 453 * 454 * @param w the desired width 455 * @param h the desired height 456 * @param initWeight the desired weight 457 */ 458 public LayoutParams(int w, int h, float initWeight) { 459 super(w, h, initWeight); 460 column = -1; 461 span = 1; 462 } 463 464 /** 465 * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams} 466 * and the child height to 467 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p> 468 */ 469 public LayoutParams() { 470 super(MATCH_PARENT, WRAP_CONTENT); 471 column = -1; 472 span = 1; 473 } 474 475 /** 476 * <p>Puts the view in the specified column.</p> 477 * 478 * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} 479 * and the child height to 480 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p> 481 * 482 * @param column the column index for the view 483 */ 484 public LayoutParams(int column) { 485 this(); 486 this.column = column; 487 } 488 489 /** 490 * {@inheritDoc} 491 */ 492 public LayoutParams(ViewGroup.LayoutParams p) { 493 super(p); 494 } 495 496 /** 497 * {@inheritDoc} 498 */ 499 public LayoutParams(MarginLayoutParams source) { 500 super(source); 501 } 502 503 @Override 504 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { 505 // We don't want to force users to specify a layout_width 506 if (a.hasValue(widthAttr)) { 507 width = a.getLayoutDimension(widthAttr, "layout_width"); 508 } else { 509 width = MATCH_PARENT; 510 } 511 512 // We don't want to force users to specify a layout_height 513 if (a.hasValue(heightAttr)) { 514 height = a.getLayoutDimension(heightAttr, "layout_height"); 515 } else { 516 height = WRAP_CONTENT; 517 } 518 } 519 } 520 521 // special transparent hierarchy change listener 522 private class ChildrenTracker implements OnHierarchyChangeListener { 523 private OnHierarchyChangeListener listener; 524 525 private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { 526 this.listener = listener; 527 } 528 529 public void onChildViewAdded(View parent, View child) { 530 // dirties the index to column map 531 mColumnToChildIndex = null; 532 533 if (this.listener != null) { 534 this.listener.onChildViewAdded(parent, child); 535 } 536 } 537 538 public void onChildViewRemoved(View parent, View child) { 539 // dirties the index to column map 540 mColumnToChildIndex = null; 541 542 if (this.listener != null) { 543 this.listener.onChildViewRemoved(parent, child); 544 } 545 } 546 } 547 } 548