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