1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
17
18import android.content.Context;
19import android.graphics.drawable.Drawable;
20import android.support.annotation.RestrictTo;
21import android.util.AttributeSet;
22import android.view.Gravity;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.FrameLayout;
26
27/**
28 * Subclass of FrameLayout that support scale layout area size for children.
29 * @hide
30 */
31@RestrictTo(LIBRARY_GROUP)
32public class ScaleFrameLayout extends FrameLayout {
33
34    private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
35
36    private float mLayoutScaleX = 1f;
37    private float mLayoutScaleY = 1f;
38
39    private float mChildScale = 1f;
40
41    public ScaleFrameLayout(Context context) {
42        this(context ,null);
43    }
44
45    public ScaleFrameLayout(Context context, AttributeSet attrs) {
46        this(context, attrs, 0);
47    }
48
49    public ScaleFrameLayout(Context context, AttributeSet attrs,
50            int defStyle) {
51        super(context, attrs, defStyle);
52    }
53
54    public void setLayoutScaleX(float scaleX) {
55        if (scaleX != mLayoutScaleX) {
56            mLayoutScaleX = scaleX;
57            requestLayout();
58        }
59    }
60
61    public void setLayoutScaleY(float scaleY) {
62        if (scaleY != mLayoutScaleY) {
63            mLayoutScaleY = scaleY;
64            requestLayout();
65        }
66    }
67
68    public void setChildScale(float scale) {
69        if (mChildScale != scale) {
70            mChildScale = scale;
71            for (int i = 0; i < getChildCount(); i++) {
72                getChildAt(i).setScaleX(scale);
73                getChildAt(i).setScaleY(scale);
74            }
75        }
76    }
77
78    @Override
79    public void addView(View child, int index, ViewGroup.LayoutParams params) {
80        super.addView(child, index, params);
81        child.setScaleX(mChildScale);
82        child.setScaleY(mChildScale);
83    }
84
85    @Override
86    protected boolean addViewInLayout (View child, int index, ViewGroup.LayoutParams params,
87            boolean preventRequestLayout) {
88        boolean ret = super.addViewInLayout(child, index, params, preventRequestLayout);
89        if (ret) {
90            child.setScaleX(mChildScale);
91            child.setScaleY(mChildScale);
92        }
93        return ret;
94    }
95
96    @Override
97    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
98        final int count = getChildCount();
99
100        final int parentLeft, parentRight;
101        final int layoutDirection = getLayoutDirection();
102        final float pivotX = (layoutDirection == View.LAYOUT_DIRECTION_RTL)
103                ? getWidth() - getPivotX()
104                : getPivotX();
105        if (mLayoutScaleX != 1f) {
106            parentLeft = getPaddingLeft() + (int)(pivotX - pivotX / mLayoutScaleX + 0.5f);
107            parentRight = (int)(pivotX + (right - left - pivotX) / mLayoutScaleX + 0.5f)
108                    - getPaddingRight();
109        } else {
110            parentLeft = getPaddingLeft();
111            parentRight = right - left - getPaddingRight();
112        }
113
114        final int parentTop, parentBottom;
115        final float pivotY = getPivotY();
116        if (mLayoutScaleY != 1f) {
117            parentTop = getPaddingTop() + (int)(pivotY - pivotY / mLayoutScaleY + 0.5f);
118            parentBottom = (int)(pivotY + (bottom - top - pivotY) / mLayoutScaleY + 0.5f)
119                    - getPaddingBottom();
120        } else {
121            parentTop = getPaddingTop();
122            parentBottom = bottom - top - getPaddingBottom();
123        }
124
125        for (int i = 0; i < count; i++) {
126            final View child = getChildAt(i);
127            if (child.getVisibility() != GONE) {
128                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
129
130                final int width = child.getMeasuredWidth();
131                final int height = child.getMeasuredHeight();
132
133                int childLeft;
134                int childTop;
135
136                int gravity = lp.gravity;
137                if (gravity == -1) {
138                    gravity = DEFAULT_CHILD_GRAVITY;
139                }
140
141                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
142                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
143
144                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
145                    case Gravity.CENTER_HORIZONTAL:
146                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2
147                                + lp.leftMargin - lp.rightMargin;
148                        break;
149                    case Gravity.RIGHT:
150                        childLeft = parentRight - width - lp.rightMargin;
151                        break;
152                    case Gravity.LEFT:
153                    default:
154                        childLeft = parentLeft + lp.leftMargin;
155                }
156
157                switch (verticalGravity) {
158                    case Gravity.TOP:
159                        childTop = parentTop + lp.topMargin;
160                        break;
161                    case Gravity.CENTER_VERTICAL:
162                        childTop = parentTop + (parentBottom - parentTop - height) / 2
163                                + lp.topMargin - lp.bottomMargin;
164                        break;
165                    case Gravity.BOTTOM:
166                        childTop = parentBottom - height - lp.bottomMargin;
167                        break;
168                    default:
169                        childTop = parentTop + lp.topMargin;
170                }
171
172                child.layout(childLeft, childTop, childLeft + width, childTop + height);
173                // synchronize child pivot to be same as ScaleFrameLayout's pivot
174                child.setPivotX(pivotX - childLeft);
175                child.setPivotY(pivotY - childTop);
176            }
177        }
178    }
179
180    private static int getScaledMeasureSpec(int measureSpec, float scale) {
181        return scale == 1f ? measureSpec : MeasureSpec.makeMeasureSpec(
182                (int) (MeasureSpec.getSize(measureSpec) / scale + 0.5f),
183                MeasureSpec.getMode(measureSpec));
184    }
185
186    @Override
187    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
188        if (mLayoutScaleX != 1f || mLayoutScaleY != 1f) {
189            final int scaledWidthMeasureSpec =
190                    getScaledMeasureSpec(widthMeasureSpec, mLayoutScaleX);
191            final int scaledHeightMeasureSpec =
192                    getScaledMeasureSpec(heightMeasureSpec, mLayoutScaleY);
193            super.onMeasure(scaledWidthMeasureSpec, scaledHeightMeasureSpec);
194            setMeasuredDimension((int)(getMeasuredWidth() * mLayoutScaleX + 0.5f),
195                    (int)(getMeasuredHeight() * mLayoutScaleY + 0.5f));
196        } else {
197            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
198        }
199    }
200
201    /**
202     * setForeground() is not supported,  throws UnsupportedOperationException() when called.
203     */
204    @Override
205    public void setForeground(Drawable d) {
206        throw new UnsupportedOperationException();
207    }
208
209}
210