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