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