1 /*
2  * Copyright (C) 2010 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 com.android.contacts.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Rect;
22 import android.util.AttributeSet;
23 import android.view.Gravity;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.LinearLayout;
27 
28 import com.android.contacts.R;
29 
30 /**
31  * Layout similar to LinearLayout that allows a child to specify examples of
32  * desired size depending on the parent size. For example if the widget wants to
33  * be 100dip when parent is 200dip and 110dip when parent is 400dip, the layout
34  * will ensure these requirements and interpolate for other parent sizes.
35  * You can also specify minWidth for each child.  You can have at most one
36  * child with layout_width="match_parent" - it will take the entire remaining
37  * space.
38  */
39 public class InterpolatingLayout extends ViewGroup {
40 
41     private Rect mInRect = new Rect();
42     private Rect mOutRect = new Rect();
43 
InterpolatingLayout(Context context)44     public InterpolatingLayout(Context context) {
45         super(context);
46     }
47 
InterpolatingLayout(Context context, AttributeSet attrs)48     public InterpolatingLayout(Context context, AttributeSet attrs) {
49         super(context, attrs);
50     }
51 
InterpolatingLayout(Context context, AttributeSet attrs, int defStyle)52     public InterpolatingLayout(Context context, AttributeSet attrs, int defStyle) {
53         super(context, attrs, defStyle);
54     }
55 
56     public final static class LayoutParams extends LinearLayout.LayoutParams {
57 
58         public int narrowParentWidth;
59         public int narrowWidth;
60         public int narrowMarginLeft;
61         public int narrowPaddingLeft;
62         public int narrowMarginRight;
63         public int narrowPaddingRight;
64         public int wideParentWidth;
65         public int wideWidth;
66         public int wideMarginLeft;
67         public int widePaddingLeft;
68         public int wideMarginRight;
69         public int widePaddingRight;
70         private float widthMultiplier;
71         private int widthConstant;
72         private float leftMarginMultiplier;
73         private int leftMarginConstant;
74         private float leftPaddingMultiplier;
75         private int leftPaddingConstant;
76         private float rightMarginMultiplier;
77         private int rightMarginConstant;
78         private float rightPaddingMultiplier;
79         private int rightPaddingConstant;
80 
LayoutParams(Context c, AttributeSet attrs)81         public LayoutParams(Context c, AttributeSet attrs) {
82             super(c, attrs);
83             TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.InterpolatingLayout_Layout);
84 
85             narrowParentWidth = a.getDimensionPixelSize(
86                     R.styleable.InterpolatingLayout_Layout_layout_narrowParentWidth, -1);
87             narrowWidth = a.getDimensionPixelSize(
88                     R.styleable.InterpolatingLayout_Layout_layout_narrowWidth, -1);
89             narrowMarginLeft = a.getDimensionPixelSize(
90                     R.styleable.InterpolatingLayout_Layout_layout_narrowMarginLeft, -1);
91             narrowPaddingLeft = a.getDimensionPixelSize(
92                     R.styleable.InterpolatingLayout_Layout_layout_narrowPaddingLeft, -1);
93             narrowMarginRight = a.getDimensionPixelSize(
94                     R.styleable.InterpolatingLayout_Layout_layout_narrowMarginRight, -1);
95             narrowPaddingRight = a.getDimensionPixelSize(
96                     R.styleable.InterpolatingLayout_Layout_layout_narrowPaddingRight, -1);
97             wideParentWidth = a.getDimensionPixelSize(
98                     R.styleable.InterpolatingLayout_Layout_layout_wideParentWidth, -1);
99             wideWidth = a.getDimensionPixelSize(
100                     R.styleable.InterpolatingLayout_Layout_layout_wideWidth, -1);
101             wideMarginLeft = a.getDimensionPixelSize(
102                     R.styleable.InterpolatingLayout_Layout_layout_wideMarginLeft, -1);
103             widePaddingLeft = a.getDimensionPixelSize(
104                     R.styleable.InterpolatingLayout_Layout_layout_widePaddingLeft, -1);
105             wideMarginRight = a.getDimensionPixelSize(
106                     R.styleable.InterpolatingLayout_Layout_layout_wideMarginRight, -1);
107             widePaddingRight = a.getDimensionPixelSize(
108                     R.styleable.InterpolatingLayout_Layout_layout_widePaddingRight, -1);
109 
110             a.recycle();
111 
112             if (narrowWidth != -1) {
113                 widthMultiplier = (float) (wideWidth - narrowWidth)
114                         / (wideParentWidth - narrowParentWidth);
115                 widthConstant = (int) (narrowWidth - narrowParentWidth * widthMultiplier);
116             }
117 
118             if (narrowMarginLeft != -1) {
119                 leftMarginMultiplier = (float) (wideMarginLeft - narrowMarginLeft)
120                         / (wideParentWidth - narrowParentWidth);
121                 leftMarginConstant = (int) (narrowMarginLeft - narrowParentWidth
122                         * leftMarginMultiplier);
123             }
124 
125             if (narrowPaddingLeft != -1) {
126                 leftPaddingMultiplier = (float) (widePaddingLeft - narrowPaddingLeft)
127                         / (wideParentWidth - narrowParentWidth);
128                 leftPaddingConstant = (int) (narrowPaddingLeft - narrowParentWidth
129                         * leftPaddingMultiplier);
130             }
131 
132             if (narrowMarginRight != -1) {
133                 rightMarginMultiplier = (float) (wideMarginRight - narrowMarginRight)
134                         / (wideParentWidth - narrowParentWidth);
135                 rightMarginConstant = (int) (narrowMarginRight - narrowParentWidth
136                         * rightMarginMultiplier);
137             }
138 
139             if (narrowPaddingRight != -1) {
140                 rightPaddingMultiplier = (float) (widePaddingRight - narrowPaddingRight)
141                         / (wideParentWidth - narrowParentWidth);
142                 rightPaddingConstant = (int) (narrowPaddingRight - narrowParentWidth
143                         * rightPaddingMultiplier);
144             }
145         }
146 
LayoutParams(int width, int height)147         public LayoutParams(int width, int height) {
148             super(width, height);
149         }
150 
LayoutParams(MarginLayoutParams source)151         public LayoutParams(MarginLayoutParams source) {
152             super(source);
153         }
154 
resolveWidth(int parentSize)155         public int resolveWidth(int parentSize) {
156             if (narrowWidth == -1) {
157                 return width;
158             } else {
159                 int w = (int) (parentSize * widthMultiplier) + widthConstant;
160                 return w <= 0 ? WRAP_CONTENT : w;
161             }
162         }
163 
resolveLeftMargin(int parentSize)164         public int resolveLeftMargin(int parentSize) {
165             if (narrowMarginLeft == -1) {
166                 return leftMargin;
167             } else {
168                 int w = (int) (parentSize * leftMarginMultiplier) + leftMarginConstant;
169                 return w < 0 ? 0 : w;
170             }
171         }
172 
resolveLeftPadding(int parentSize)173         public int resolveLeftPadding(int parentSize) {
174             int w = (int) (parentSize * leftPaddingMultiplier) + leftPaddingConstant;
175             return w < 0 ? 0 : w;
176         }
177 
resolveRightMargin(int parentSize)178         public int resolveRightMargin(int parentSize) {
179             if (narrowMarginRight == -1) {
180                 return rightMargin;
181             } else {
182                 int w = (int) (parentSize * rightMarginMultiplier) + rightMarginConstant;
183                 return w < 0 ? 0 : w;
184             }
185         }
186 
resolveRightPadding(int parentSize)187         public int resolveRightPadding(int parentSize) {
188             int w = (int) (parentSize * rightPaddingMultiplier) + rightPaddingConstant;
189             return w < 0 ? 0 : w;
190         }
191     }
192 
193     @Override
generateLayoutParams(AttributeSet attrs)194     public LayoutParams generateLayoutParams(AttributeSet attrs) {
195         return new LayoutParams(getContext(), attrs);
196     }
197 
198     @Override
generateDefaultLayoutParams()199     protected LayoutParams generateDefaultLayoutParams() {
200         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
201     }
202 
203     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)204     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
205         int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
206         int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
207 
208         int width = 0;
209         int height = 0;
210 
211         View fillChild = null;
212         int count = getChildCount();
213         for (int i = 0; i < count; i++) {
214             View child = getChildAt(i);
215             if (child.getVisibility() == View.GONE) {
216                 continue;
217             }
218 
219             LayoutParams params = (LayoutParams) child.getLayoutParams();
220             if (params.width == LayoutParams.MATCH_PARENT) {
221                 if (fillChild != null) {
222                     throw new RuntimeException(
223                             "Interpolating layout allows at most one child"
224                             + " with layout_width='match_parent'");
225                 }
226                 fillChild = child;
227             } else {
228                 int childWidth = params.resolveWidth(parentWidth);
229                 int childWidthMeasureSpec;
230                 switch (childWidth) {
231                     case LayoutParams.WRAP_CONTENT:
232                         childWidthMeasureSpec = MeasureSpec.UNSPECIFIED;
233                         break;
234                     default:
235                         childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
236                                 childWidth, MeasureSpec.EXACTLY);
237                         break;
238                 }
239 
240                 int childHeightMeasureSpec;
241                 switch (params.height) {
242                     case LayoutParams.WRAP_CONTENT:
243                         childHeightMeasureSpec = MeasureSpec.UNSPECIFIED;
244                         break;
245                     case LayoutParams.MATCH_PARENT:
246                         childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
247                                 parentHeight - params.topMargin - params.bottomMargin,
248                                 MeasureSpec.EXACTLY);
249                         break;
250                     default:
251                         childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
252                                 params.height, MeasureSpec.EXACTLY);
253                         break;
254                 }
255 
256                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
257                 width += child.getMeasuredWidth();
258                 height = Math.max(child.getMeasuredHeight(), height);
259             }
260 
261             width += params.resolveLeftMargin(parentWidth) + params.resolveRightMargin(parentWidth);
262         }
263 
264         if (fillChild != null) {
265             int remainder = parentWidth - width;
266             int childMeasureSpec = remainder > 0
267                     ? MeasureSpec.makeMeasureSpec(remainder, MeasureSpec.EXACTLY)
268                     : MeasureSpec.UNSPECIFIED;
269             fillChild.measure(childMeasureSpec, heightMeasureSpec);
270             width += fillChild.getMeasuredWidth();
271             height = Math.max(fillChild.getMeasuredHeight(), height);
272         }
273 
274         setMeasuredDimension(
275                 resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec));
276     }
277 
278     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)279     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
280         int offset = 0;
281         int width = right - left;
282         int count = getChildCount();
283         for (int i = 0; i < count; i++) {
284             View child = getChildAt(i);
285 
286             if (child.getVisibility() == View.GONE) {
287                 continue;
288             }
289 
290             LayoutParams params = (LayoutParams) child.getLayoutParams();
291             int gravity = params.gravity;
292             if (gravity == -1) {
293                 gravity = Gravity.START | Gravity.TOP;
294             }
295 
296             if (params.narrowPaddingLeft != -1 || params.narrowPaddingRight != -1) {
297                 int leftPadding = params.narrowPaddingLeft == -1 ? child.getPaddingLeft()
298                         : params.resolveLeftPadding(width);
299                 int rightPadding = params.narrowPaddingRight == -1 ? child.getPaddingRight()
300                         : params.resolveRightPadding(width);
301                 child.setPadding(
302                         leftPadding, child.getPaddingTop(), rightPadding, child.getPaddingBottom());
303             }
304 
305             int leftMargin = params.resolveLeftMargin(width);
306             int rightMargin = params.resolveRightMargin(width);
307 
308             mInRect.set(offset + leftMargin, params.topMargin,
309                     right - rightMargin, bottom - params.bottomMargin);
310 
311             Gravity.apply(gravity, child.getMeasuredWidth(), child.getMeasuredHeight(),
312                     mInRect, mOutRect);
313             child.layout(mOutRect.left, mOutRect.top, mOutRect.right, mOutRect.bottom);
314 
315             offset = mOutRect.right + rightMargin;
316         }
317     }
318 }
319