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