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