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.view.ViewGroup;
23 import android.widget.FrameLayout;
24 
25 import androidx.annotation.RequiresApi;
26 
27 /**
28  * Subclass of {@link android.widget.FrameLayout} that supports percentage based dimensions and
29  * margins.
30  *
31  * You can specify dimension or a margin of child by using attributes with "Percent" suffix. Follow
32  * this example:
33  *
34  * <pre class="prettyprint">
35  * &lt;androidx.percentlayout.widget.PercentFrameLayout
36  *         xmlns:android="http://schemas.android.com/apk/res/android"
37  *         xmlns:app="http://schemas.android.com/apk/res-auto"
38  *         android:layout_width="match_parent"
39  *         android:layout_height="match_parent"&gt
40  *     &lt;ImageView
41  *         app:layout_widthPercent="50%"
42  *         app:layout_heightPercent="50%"
43  *         app:layout_marginTopPercent="25%"
44  *         app:layout_marginLeftPercent="25%"/&gt
45  * &lt;/androidx.percentlayout.widget.PercentFrameLayout&gt
46  * </pre>
47  *
48  * The attributes that you can use are:
49  * <ul>
50  *     <li>{@code layout_widthPercent}
51  *     <li>{@code layout_heightPercent}
52  *     <li>{@code layout_marginPercent}
53  *     <li>{@code layout_marginLeftPercent}
54  *     <li>{@code layout_marginTopPercent}
55  *     <li>{@code layout_marginRightPercent}
56  *     <li>{@code layout_marginBottomPercent}
57  *     <li>{@code layout_marginStartPercent}
58  *     <li>{@code layout_marginEndPercent}
59  *     <li>{@code layout_aspectRatio}
60  * </ul>
61  *
62  * It is not necessary to specify {@code layout_width/height} if you specify {@code
63  * layout_widthPercent.} However, if you want the view to be able to take up more space than what
64  * percentage value permits, you can add {@code layout_width/height="wrap_content"}. In that case
65  * if the percentage size is too small for the View's content, it will be resized using
66  * {@code wrap_content} rule.
67  *
68  * <p>
69  * You can also make one dimension be a fraction of the other by setting only width or height and
70  * using {@code layout_aspectRatio} for the second one to be calculated automatically. For
71  * example, if you would like to achieve 16:9 aspect ratio, you can write:
72  * <pre class="prettyprint">
73  *     android:layout_width="300dp"
74  *     app:layout_aspectRatio="178%"
75  * </pre>
76  * This will make the aspect ratio 16:9 (1.78:1) with the width fixed at 300dp and height adjusted
77  * accordingly.
78  *
79  * @deprecated consider using ConstraintLayout and associated layouts instead. The following shows
80  * how to replicate the functionality of percentage layouts with a ConstraintLayout. The Guidelines
81  * are used to define each percentage break point, and then a Button view is stretched to fill
82  * the gap:
83  *
84  * <pre class="prettyprint">
85  * &lt;androidx.constraintlayout.widget.ConstraintLayout
86  *         xmlns:android="http://schemas.android.com/apk/res/android"
87  *         xmlns:app="http://schemas.android.com/apk/res-auto"
88  *         android:layout_width="match_parent"
89  *         android:layout_height="match_parent"&gt
90  *
91  *     &lt;androidx.constraintlayout.widget.Guideline
92  *         android:layout_width="wrap_content"
93  *         android:layout_height="wrap_content"
94  *         android:id="@+id/left_guideline"
95  *         app:layout_constraintGuide_percent=".15"
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/right_guideline"
102  *         app:layout_constraintGuide_percent=".85"
103  *         android:orientation="vertical"/&gt
104  *
105  *     &lt;androidx.constraintlayout.widget.Guideline
106  *         android:layout_width="wrap_content"
107  *         android:layout_height="wrap_content"
108  *         android:id="@+id/top_guideline"
109  *         app:layout_constraintGuide_percent=".15"
110  *         android:orientation="horizontal"/&gt
111  *
112  *     &lt;androidx.constraintlayout.widget.Guideline
113  *         android:layout_width="wrap_content"
114  *         android:layout_height="wrap_content"
115  *         android:id="@+id/bottom_guideline"
116  *         app:layout_constraintGuide_percent=".85"
117  *         android:orientation="horizontal"/&gt
118  *
119  *     &lt;Button
120  *         android:text="Button"
121  *         android:layout_width="0dp"
122  *         android:layout_height="0dp"
123  *         android:id="@+id/button"
124  *         app:layout_constraintLeft_toLeftOf="@+id/left_guideline"
125  *         app:layout_constraintRight_toRightOf="@+id/right_guideline"
126  *         app:layout_constraintTop_toTopOf="@+id/top_guideline"
127  *         app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" /&gt
128  *
129  * &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt
130  * </pre>
131  */
132 @Deprecated
133 public class PercentFrameLayout extends FrameLayout {
134     private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);
135 
PercentFrameLayout(Context context)136     public PercentFrameLayout(Context context) {
137         super(context);
138     }
139 
PercentFrameLayout(Context context, AttributeSet attrs)140     public PercentFrameLayout(Context context, AttributeSet attrs) {
141         super(context, attrs);
142     }
143 
PercentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr)144     public PercentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
145         super(context, attrs, defStyleAttr);
146     }
147 
148     @Override
generateDefaultLayoutParams()149     protected LayoutParams generateDefaultLayoutParams() {
150         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
151     }
152 
153     @Override
generateLayoutParams(AttributeSet attrs)154     public LayoutParams generateLayoutParams(AttributeSet attrs) {
155         return new LayoutParams(getContext(), attrs);
156     }
157 
158     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)159     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
160         mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
161         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
162         if (mHelper.handleMeasuredStateTooSmall()) {
163             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
164         }
165     }
166 
167     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)168     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
169         super.onLayout(changed, left, top, right, bottom);
170         mHelper.restoreOriginalParams();
171     }
172 
173     /**
174      * @deprecated this class is deprecated along with its parent class.
175      */
176     @Deprecated
177     public static class LayoutParams extends FrameLayout.LayoutParams
178             implements PercentLayoutHelper.PercentLayoutParams {
179         private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;
180 
LayoutParams(Context c, AttributeSet attrs)181         public LayoutParams(Context c, AttributeSet attrs) {
182             super(c, attrs);
183             mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
184         }
185 
LayoutParams(int width, int height)186         public LayoutParams(int width, int height) {
187             super(width, height);
188         }
189 
LayoutParams(int width, int height, int gravity)190         public LayoutParams(int width, int height, int gravity) {
191             super(width, height, gravity);
192         }
193 
LayoutParams(ViewGroup.LayoutParams source)194         public LayoutParams(ViewGroup.LayoutParams source) {
195             super(source);
196         }
197 
LayoutParams(MarginLayoutParams source)198         public LayoutParams(MarginLayoutParams source) {
199             super(source);
200         }
201 
LayoutParams(FrameLayout.LayoutParams source)202         public LayoutParams(FrameLayout.LayoutParams source) {
203             super((MarginLayoutParams) source);
204             gravity = source.gravity;
205         }
206 
207         @RequiresApi(19)
LayoutParams(LayoutParams source)208         public LayoutParams(LayoutParams source) {
209             // The copy constructor used here is only supported on API 19+.
210             this((FrameLayout.LayoutParams) source);
211             mPercentLayoutInfo = source.mPercentLayoutInfo;
212         }
213 
214         @Override
getPercentLayoutInfo()215         public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
216             if (mPercentLayoutInfo == null) {
217                 mPercentLayoutInfo = new PercentLayoutHelper.PercentLayoutInfo();
218             }
219 
220             return mPercentLayoutInfo;
221         }
222 
223         @Override
setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)224         protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
225             PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
226         }
227     }
228 }
229