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
17package com.android.contacts.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Rect;
22import android.util.AttributeSet;
23import android.view.Gravity;
24import android.view.View;
25import android.view.ViewGroup;
26import android.widget.LinearLayout;
27
28import 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 */
39public class InterpolatingLayout extends ViewGroup {
40
41    private Rect mInRect = new Rect();
42    private Rect mOutRect = new Rect();
43
44    public InterpolatingLayout(Context context) {
45        super(context);
46    }
47
48    public InterpolatingLayout(Context context, AttributeSet attrs) {
49        super(context, attrs);
50    }
51
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
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
147        public LayoutParams(int width, int height) {
148            super(width, height);
149        }
150
151        public LayoutParams(MarginLayoutParams source) {
152            super(source);
153        }
154
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
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
173        public int resolveLeftPadding(int parentSize) {
174            int w = (int) (parentSize * leftPaddingMultiplier) + leftPaddingConstant;
175            return w < 0 ? 0 : w;
176        }
177
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
187        public int resolveRightPadding(int parentSize) {
188            int w = (int) (parentSize * rightPaddingMultiplier) + rightPaddingConstant;
189            return w < 0 ? 0 : w;
190        }
191    }
192
193    @Override
194    public LayoutParams generateLayoutParams(AttributeSet attrs) {
195        return new LayoutParams(getContext(), attrs);
196    }
197
198    @Override
199    protected LayoutParams generateDefaultLayoutParams() {
200        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
201    }
202
203    @Override
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
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