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