1 /* 2 * Copyright (C) 2015 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.support.percent; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.support.annotation.NonNull; 22 import android.support.v4.view.MarginLayoutParamsCompat; 23 import android.support.v4.view.ViewCompat; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.View; 27 import android.view.ViewGroup; 28 29 import android.support.percent.R; 30 31 import java.lang.Math; 32 33 /** 34 * Helper for layouts that want to support percentage based dimensions. 35 * 36 * <p>This class collects utility methods that are involved in extracting percentage based dimension 37 * attributes and applying them to ViewGroup's children. If you would like to implement a layout 38 * that supports percentage based dimensions, you need to take several steps: 39 * 40 * <ol> 41 * <li> You need a {@link ViewGroup.LayoutParams} subclass in your ViewGroup that implements 42 * {@link android.support.percent.PercentLayoutHelper.PercentLayoutParams}. 43 * <li> In your {@code LayoutParams(Context c, AttributeSet attrs)} constructor create an instance 44 * of {@link PercentLayoutHelper.PercentLayoutInfo} by calling 45 * {@link PercentLayoutHelper#getPercentLayoutInfo(Context, AttributeSet)}. Return this 46 * object from {@code public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo()} 47 * method that you implemented for {@link android.support.percent.PercentLayoutHelper.PercentLayoutParams} interface. 48 * <li> Override 49 * {@link ViewGroup.LayoutParams#setBaseAttributes(TypedArray, int, int)} 50 * with a single line implementation {@code PercentLayoutHelper.fetchWidthAndHeight(this, a, 51 * widthAttr, heightAttr);} 52 * <li> In your ViewGroup override {@link ViewGroup#generateLayoutParams(AttributeSet)} to return 53 * your LayoutParams. 54 * <li> In your {@link ViewGroup#onMeasure(int, int)} override, you need to implement following 55 * pattern: 56 * <pre class="prettyprint"> 57 * protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 58 * mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); 59 * super.onMeasure(widthMeasureSpec, heightMeasureSpec); 60 * if (mHelper.handleMeasuredStateTooSmall()) { 61 * super.onMeasure(widthMeasureSpec, heightMeasureSpec); 62 * } 63 * } 64 * </pre> 65 * <li>In your {@link ViewGroup#onLayout(boolean, int, int, int, int)} override, you need to 66 * implement following pattern: 67 * <pre class="prettyprint"> 68 * protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 69 * super.onLayout(changed, left, top, right, bottom); 70 * mHelper.restoreOriginalParams(); 71 * } 72 * </pre> 73 * </ol> 74 */ 75 public class PercentLayoutHelper { 76 private static final String TAG = "PercentLayout"; 77 78 private static final boolean DEBUG = false; 79 private static final boolean VERBOSE = false; 80 81 private final ViewGroup mHost; 82 PercentLayoutHelper(@onNull ViewGroup host)83 public PercentLayoutHelper(@NonNull ViewGroup host) { 84 if (host == null) { 85 throw new IllegalArgumentException("host must be non-null"); 86 } 87 mHost = host; 88 } 89 90 /** 91 * Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override 92 * that reads layout_width and layout_height attribute values without throwing an exception if 93 * they aren't present. 94 */ fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array, int widthAttr, int heightAttr)95 public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array, 96 int widthAttr, int heightAttr) { 97 params.width = array.getLayoutDimension(widthAttr, 0); 98 params.height = array.getLayoutDimension(heightAttr, 0); 99 } 100 101 /** 102 * Iterates over children and changes their width and height to one calculated from percentage 103 * values. 104 * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup. 105 * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup. 106 */ adjustChildren(int widthMeasureSpec, int heightMeasureSpec)107 public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) { 108 if (DEBUG) { 109 Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: " 110 + View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: " 111 + View.MeasureSpec.toString(heightMeasureSpec)); 112 } 113 114 // Calculate available space, accounting for host's paddings 115 int widthHint = View.MeasureSpec.getSize(widthMeasureSpec) - mHost.getPaddingLeft() 116 - mHost.getPaddingRight(); 117 int heightHint = View.MeasureSpec.getSize(heightMeasureSpec) - mHost.getPaddingTop() 118 - mHost.getPaddingBottom(); 119 for (int i = 0, N = mHost.getChildCount(); i < N; i++) { 120 View view = mHost.getChildAt(i); 121 ViewGroup.LayoutParams params = view.getLayoutParams(); 122 if (DEBUG) { 123 Log.d(TAG, "should adjust " + view + " " + params); 124 } 125 if (params instanceof PercentLayoutParams) { 126 PercentLayoutInfo info = 127 ((PercentLayoutParams) params).getPercentLayoutInfo(); 128 if (DEBUG) { 129 Log.d(TAG, "using " + info); 130 } 131 if (info != null) { 132 if (params instanceof ViewGroup.MarginLayoutParams) { 133 info.fillMarginLayoutParams(view, (ViewGroup.MarginLayoutParams) params, 134 widthHint, heightHint); 135 } else { 136 info.fillLayoutParams(params, widthHint, heightHint); 137 } 138 } 139 } 140 } 141 } 142 143 /** 144 * Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from 145 * {@code LayoutParams(Context c, AttributeSet attrs)} constructor. 146 */ getPercentLayoutInfo(Context context, AttributeSet attrs)147 public static PercentLayoutInfo getPercentLayoutInfo(Context context, 148 AttributeSet attrs) { 149 PercentLayoutInfo info = null; 150 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout); 151 float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1, 152 -1f); 153 if (value != -1f) { 154 if (VERBOSE) { 155 Log.v(TAG, "percent width: " + value); 156 } 157 info = info != null ? info : new PercentLayoutInfo(); 158 info.widthPercent = value; 159 } 160 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f); 161 if (value != -1f) { 162 if (VERBOSE) { 163 Log.v(TAG, "percent height: " + value); 164 } 165 info = info != null ? info : new PercentLayoutInfo(); 166 info.heightPercent = value; 167 } 168 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f); 169 if (value != -1f) { 170 if (VERBOSE) { 171 Log.v(TAG, "percent margin: " + value); 172 } 173 info = info != null ? info : new PercentLayoutInfo(); 174 info.leftMarginPercent = value; 175 info.topMarginPercent = value; 176 info.rightMarginPercent = value; 177 info.bottomMarginPercent = value; 178 } 179 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1, 180 -1f); 181 if (value != -1f) { 182 if (VERBOSE) { 183 Log.v(TAG, "percent left margin: " + value); 184 } 185 info = info != null ? info : new PercentLayoutInfo(); 186 info.leftMarginPercent = value; 187 } 188 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1, 189 -1f); 190 if (value != -1f) { 191 if (VERBOSE) { 192 Log.v(TAG, "percent top margin: " + value); 193 } 194 info = info != null ? info : new PercentLayoutInfo(); 195 info.topMarginPercent = value; 196 } 197 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1, 198 -1f); 199 if (value != -1f) { 200 if (VERBOSE) { 201 Log.v(TAG, "percent right margin: " + value); 202 } 203 info = info != null ? info : new PercentLayoutInfo(); 204 info.rightMarginPercent = value; 205 } 206 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1, 207 -1f); 208 if (value != -1f) { 209 if (VERBOSE) { 210 Log.v(TAG, "percent bottom margin: " + value); 211 } 212 info = info != null ? info : new PercentLayoutInfo(); 213 info.bottomMarginPercent = value; 214 } 215 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1, 216 -1f); 217 if (value != -1f) { 218 if (VERBOSE) { 219 Log.v(TAG, "percent start margin: " + value); 220 } 221 info = info != null ? info : new PercentLayoutInfo(); 222 info.startMarginPercent = value; 223 } 224 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1, 225 -1f); 226 if (value != -1f) { 227 if (VERBOSE) { 228 Log.v(TAG, "percent end margin: " + value); 229 } 230 info = info != null ? info : new PercentLayoutInfo(); 231 info.endMarginPercent = value; 232 } 233 234 value = array.getFraction(R.styleable.PercentLayout_Layout_layout_aspectRatio, 1, 1, -1f); 235 if (value != -1f) { 236 if (VERBOSE) { 237 Log.v(TAG, "aspect ratio: " + value); 238 } 239 info = info != null ? info : new PercentLayoutInfo(); 240 info.aspectRatio = value; 241 } 242 243 array.recycle(); 244 if (DEBUG) { 245 Log.d(TAG, "constructed: " + info); 246 } 247 return info; 248 } 249 250 /** 251 * Iterates over children and restores their original dimensions that were changed for 252 * percentage values. Calling this method only makes sense if you previously called 253 * {@link PercentLayoutHelper#adjustChildren(int, int)}. 254 */ restoreOriginalParams()255 public void restoreOriginalParams() { 256 for (int i = 0, N = mHost.getChildCount(); i < N; i++) { 257 View view = mHost.getChildAt(i); 258 ViewGroup.LayoutParams params = view.getLayoutParams(); 259 if (DEBUG) { 260 Log.d(TAG, "should restore " + view + " " + params); 261 } 262 if (params instanceof PercentLayoutParams) { 263 PercentLayoutInfo info = 264 ((PercentLayoutParams) params).getPercentLayoutInfo(); 265 if (DEBUG) { 266 Log.d(TAG, "using " + info); 267 } 268 if (info != null) { 269 if (params instanceof ViewGroup.MarginLayoutParams) { 270 info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params); 271 } else { 272 info.restoreLayoutParams(params); 273 } 274 } 275 } 276 } 277 } 278 279 /** 280 * Iterates over children and checks if any of them would like to get more space than it 281 * received through the percentage dimension. 282 * 283 * If you are building a layout that supports percentage dimensions you are encouraged to take 284 * advantage of this method. The developer should be able to specify that a child should be 285 * remeasured by adding normal dimension attribute with {@code wrap_content} value. For example 286 * he might specify child's attributes as {@code app:layout_widthPercent="60%p"} and 287 * {@code android:layout_width="wrap_content"}. In this case if the child receives too little 288 * space, it will be remeasured with width set to {@code WRAP_CONTENT}. 289 * 290 * @return True if the measure phase needs to be rerun because one of the children would like 291 * to receive more space. 292 */ handleMeasuredStateTooSmall()293 public boolean handleMeasuredStateTooSmall() { 294 boolean needsSecondMeasure = false; 295 for (int i = 0, N = mHost.getChildCount(); i < N; i++) { 296 View view = mHost.getChildAt(i); 297 ViewGroup.LayoutParams params = view.getLayoutParams(); 298 if (DEBUG) { 299 Log.d(TAG, "should handle measured state too small " + view + " " + params); 300 } 301 if (params instanceof PercentLayoutParams) { 302 PercentLayoutInfo info = 303 ((PercentLayoutParams) params).getPercentLayoutInfo(); 304 if (info != null) { 305 if (shouldHandleMeasuredWidthTooSmall(view, info)) { 306 needsSecondMeasure = true; 307 params.width = ViewGroup.LayoutParams.WRAP_CONTENT; 308 } 309 if (shouldHandleMeasuredHeightTooSmall(view, info)) { 310 needsSecondMeasure = true; 311 params.height = ViewGroup.LayoutParams.WRAP_CONTENT; 312 } 313 } 314 } 315 } 316 if (DEBUG) { 317 Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure); 318 } 319 return needsSecondMeasure; 320 } 321 shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info)322 private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) { 323 int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK; 324 return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent >= 0 && 325 info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT; 326 } 327 shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info)328 private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info) { 329 int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK; 330 return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent >= 0 && 331 info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT; 332 } 333 334 /* package */ static class PercentMarginLayoutParams extends ViewGroup.MarginLayoutParams { 335 // These two flags keep track of whether we're computing the LayoutParams width and height 336 // in the fill pass based on the aspect ratio. This allows the fill pass to be re-entrant 337 // as the framework code can call onMeasure() multiple times before the onLayout() is 338 // called. Those multiple invocations of onMeasure() are not guaranteed to be called with 339 // the same set of width / height. 340 private boolean mIsHeightComputedFromAspectRatio; 341 private boolean mIsWidthComputedFromAspectRatio; 342 PercentMarginLayoutParams(int width, int height)343 public PercentMarginLayoutParams(int width, int height) { 344 super(width, height); 345 } 346 } 347 348 /** 349 * Container for information about percentage dimensions and margins. It acts as an extension 350 * for {@code LayoutParams}. 351 */ 352 public static class PercentLayoutInfo { 353 /** The decimal value of the percentage-based width. */ 354 public float widthPercent; 355 356 /** The decimal value of the percentage-based height. */ 357 public float heightPercent; 358 359 /** The decimal value of the percentage-based left margin. */ 360 public float leftMarginPercent; 361 362 /** The decimal value of the percentage-based top margin. */ 363 public float topMarginPercent; 364 365 /** The decimal value of the percentage-based right margin. */ 366 public float rightMarginPercent; 367 368 /** The decimal value of the percentage-based bottom margin. */ 369 public float bottomMarginPercent; 370 371 /** The decimal value of the percentage-based start margin. */ 372 public float startMarginPercent; 373 374 /** The decimal value of the percentage-based end margin. */ 375 public float endMarginPercent; 376 377 /** The decimal value of the percentage-based aspect ratio. */ 378 public float aspectRatio; 379 380 /* package */ final PercentMarginLayoutParams mPreservedParams; 381 PercentLayoutInfo()382 public PercentLayoutInfo() { 383 widthPercent = -1f; 384 heightPercent = -1f; 385 leftMarginPercent = -1f; 386 topMarginPercent = -1f; 387 rightMarginPercent = -1f; 388 bottomMarginPercent = -1f; 389 startMarginPercent = -1f; 390 endMarginPercent = -1f; 391 mPreservedParams = new PercentMarginLayoutParams(0, 0); 392 } 393 394 /** 395 * Fills the {@link ViewGroup.LayoutParams#width} and {@link ViewGroup.LayoutParams#height} 396 * fields of the passed {@link ViewGroup.LayoutParams} object based on currently set 397 * percentage values. 398 */ fillLayoutParams(ViewGroup.LayoutParams params, int widthHint, int heightHint)399 public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint, 400 int heightHint) { 401 // Preserve the original layout params, so we can restore them after the measure step. 402 mPreservedParams.width = params.width; 403 mPreservedParams.height = params.height; 404 405 // We assume that width/height set to 0 means that value was unset. This might not 406 // necessarily be true, as the user might explicitly set it to 0. However, we use this 407 // information only for the aspect ratio. If the user set the aspect ratio attribute, 408 // it means they accept or soon discover that it will be disregarded. 409 final boolean widthNotSet = 410 (mPreservedParams.mIsWidthComputedFromAspectRatio 411 || mPreservedParams.width == 0) && (widthPercent < 0); 412 final boolean heightNotSet = 413 (mPreservedParams.mIsHeightComputedFromAspectRatio 414 || mPreservedParams.height == 0) && (heightPercent < 0); 415 416 if (widthPercent >= 0) { 417 params.width = Math.round(widthHint * widthPercent); 418 } 419 420 if (heightPercent >= 0) { 421 params.height = Math.round(heightHint * heightPercent); 422 } 423 424 if (aspectRatio >= 0) { 425 if (widthNotSet) { 426 params.width = Math.round(params.height * aspectRatio); 427 // Keep track that we've filled the width based on the height and aspect ratio. 428 mPreservedParams.mIsWidthComputedFromAspectRatio = true; 429 } 430 if (heightNotSet) { 431 params.height = Math.round(params.width / aspectRatio); 432 // Keep track that we've filled the height based on the width and aspect ratio. 433 mPreservedParams.mIsHeightComputedFromAspectRatio = true; 434 } 435 } 436 437 if (DEBUG) { 438 Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")"); 439 } 440 } 441 442 /** 443 * @deprecated Use 444 * {@link #fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)} 445 * for proper RTL support. 446 */ 447 @Deprecated fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, int widthHint, int heightHint)448 public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, 449 int widthHint, int heightHint) { 450 fillMarginLayoutParams(null, params, widthHint, heightHint); 451 } 452 453 /** 454 * Fills the margin fields of the passed {@link ViewGroup.MarginLayoutParams} object based 455 * on currently set percentage values and the current layout direction of the passed 456 * {@link View}. 457 */ fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params, int widthHint, int heightHint)458 public void fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params, 459 int widthHint, int heightHint) { 460 fillLayoutParams(params, widthHint, heightHint); 461 462 // Preserve the original margins, so we can restore them after the measure step. 463 mPreservedParams.leftMargin = params.leftMargin; 464 mPreservedParams.topMargin = params.topMargin; 465 mPreservedParams.rightMargin = params.rightMargin; 466 mPreservedParams.bottomMargin = params.bottomMargin; 467 MarginLayoutParamsCompat.setMarginStart(mPreservedParams, 468 MarginLayoutParamsCompat.getMarginStart(params)); 469 MarginLayoutParamsCompat.setMarginEnd(mPreservedParams, 470 MarginLayoutParamsCompat.getMarginEnd(params)); 471 472 if (leftMarginPercent >= 0) { 473 params.leftMargin = Math.round(widthHint * leftMarginPercent); 474 } 475 if (topMarginPercent >= 0) { 476 params.topMargin = Math.round(heightHint * topMarginPercent); 477 } 478 if (rightMarginPercent >= 0) { 479 params.rightMargin = Math.round(widthHint * rightMarginPercent); 480 } 481 if (bottomMarginPercent >= 0) { 482 params.bottomMargin = Math.round(heightHint * bottomMarginPercent); 483 } 484 boolean shouldResolveLayoutDirection = false; 485 if (startMarginPercent >= 0) { 486 MarginLayoutParamsCompat.setMarginStart(params, 487 Math.round(widthHint * startMarginPercent)); 488 shouldResolveLayoutDirection = true; 489 } 490 if (endMarginPercent >= 0) { 491 MarginLayoutParamsCompat.setMarginEnd(params, 492 Math.round(widthHint * endMarginPercent)); 493 shouldResolveLayoutDirection = true; 494 } 495 if (shouldResolveLayoutDirection && (view != null)) { 496 // Force the resolve pass so that start / end margins are propagated to the 497 // matching left / right fields 498 MarginLayoutParamsCompat.resolveLayoutDirection(params, 499 ViewCompat.getLayoutDirection(view)); 500 } 501 if (DEBUG) { 502 Log.d(TAG, "after fillMarginLayoutParams: (" + params.width + ", " + params.height 503 + ")"); 504 } 505 } 506 507 @Override toString()508 public String toString() { 509 return String.format("PercentLayoutInformation width: %f height %f, margins (%f, %f, " 510 + " %f, %f, %f, %f)", widthPercent, heightPercent, leftMarginPercent, 511 topMarginPercent, rightMarginPercent, bottomMarginPercent, startMarginPercent, 512 endMarginPercent); 513 514 } 515 516 /** 517 * Restores the original dimensions and margins after they were changed for percentage based 518 * values. You should call this method only if you previously called 519 * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}. 520 */ restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params)521 public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) { 522 restoreLayoutParams(params); 523 params.leftMargin = mPreservedParams.leftMargin; 524 params.topMargin = mPreservedParams.topMargin; 525 params.rightMargin = mPreservedParams.rightMargin; 526 params.bottomMargin = mPreservedParams.bottomMargin; 527 MarginLayoutParamsCompat.setMarginStart(params, 528 MarginLayoutParamsCompat.getMarginStart(mPreservedParams)); 529 MarginLayoutParamsCompat.setMarginEnd(params, 530 MarginLayoutParamsCompat.getMarginEnd(mPreservedParams)); 531 } 532 533 /** 534 * Restores original dimensions after they were changed for percentage based values. 535 * You should call this method only if you previously called 536 * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams(ViewGroup.LayoutParams, int, int)}. 537 */ restoreLayoutParams(ViewGroup.LayoutParams params)538 public void restoreLayoutParams(ViewGroup.LayoutParams params) { 539 if (!mPreservedParams.mIsWidthComputedFromAspectRatio) { 540 // Only restore the width if we didn't compute it based on the height and 541 // aspect ratio in the fill pass. 542 params.width = mPreservedParams.width; 543 } 544 if (!mPreservedParams.mIsHeightComputedFromAspectRatio) { 545 // Only restore the height if we didn't compute it based on the width and 546 // aspect ratio in the fill pass. 547 params.height = mPreservedParams.height; 548 } 549 550 // Reset the tracking flags. 551 mPreservedParams.mIsWidthComputedFromAspectRatio = false; 552 mPreservedParams.mIsHeightComputedFromAspectRatio = false; 553 } 554 } 555 556 /** 557 * If a layout wants to support percentage based dimensions and use this helper class, its 558 * {@code LayoutParams} subclass must implement this interface. 559 * 560 * Your {@code LayoutParams} subclass should contain an instance of {@code PercentLayoutInfo} 561 * and the implementation of this interface should be a simple accessor. 562 */ 563 public interface PercentLayoutParams { getPercentLayoutInfo()564 PercentLayoutInfo getPercentLayoutInfo(); 565 } 566 } 567