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  */
14 package android.support.v17.leanback.widget;
15 
16 import android.content.Context;
17 import android.graphics.drawable.Drawable;
18 import android.util.AttributeSet;
19 import android.view.Gravity;
20 import android.view.View;
21 import android.widget.FrameLayout;
22 
23 /**
24  * Subclass of FrameLayout that support scale layout area size for children.
25  * @hide
26  */
27 public class ScaleFrameLayout extends FrameLayout {
28 
29     private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
30 
31     private float mLayoutScaleX = 1f;
32     private float mLayoutScaleY = 1f;
33 
ScaleFrameLayout(Context context)34     public ScaleFrameLayout(Context context) {
35         this(context ,null);
36     }
37 
ScaleFrameLayout(Context context, AttributeSet attrs)38     public ScaleFrameLayout(Context context, AttributeSet attrs) {
39         this(context, attrs, 0);
40     }
41 
ScaleFrameLayout(Context context, AttributeSet attrs, int defStyle)42     public ScaleFrameLayout(Context context, AttributeSet attrs,
43             int defStyle) {
44         super(context, attrs, defStyle);
45     }
46 
setLayoutScaleX(float scaleX)47     public void setLayoutScaleX(float scaleX) {
48         if (scaleX != mLayoutScaleX) {
49             mLayoutScaleX = scaleX;
50             requestLayout();
51         }
52     }
53 
setLayoutScaleY(float scaleY)54     public void setLayoutScaleY(float scaleY) {
55         if (scaleY != mLayoutScaleY) {
56             mLayoutScaleY = scaleY;
57             requestLayout();
58         }
59     }
60 
61     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)62     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
63         final int count = getChildCount();
64 
65         final int parentLeft, parentRight;
66         final int layoutDirection = getLayoutDirection();
67         final float pivotX = (layoutDirection == View.LAYOUT_DIRECTION_RTL) ?
68                 getWidth() - getPivotX() :
69                 getPivotX();
70         if (mLayoutScaleX != 1f) {
71             parentLeft = getPaddingLeft() + (int)(pivotX - pivotX / mLayoutScaleX + 0.5f);
72             parentRight = (int)(pivotX + (right - left - pivotX) / mLayoutScaleX + 0.5f)
73                     - getPaddingRight();
74         } else {
75             parentLeft = getPaddingLeft();
76             parentRight = right - left - getPaddingRight();
77         }
78 
79         final int parentTop, parentBottom;
80         final float pivotY = getPivotY();
81         if (mLayoutScaleY != 1f) {
82             parentTop = getPaddingTop() + (int)(pivotY - pivotY / mLayoutScaleY + 0.5f);
83             parentBottom = (int)(pivotY + (bottom - top - pivotY) / mLayoutScaleY + 0.5f)
84                     - getPaddingBottom();
85         } else {
86             parentTop = getPaddingTop();
87             parentBottom = bottom - top - getPaddingBottom();
88         }
89 
90         for (int i = 0; i < count; i++) {
91             final View child = getChildAt(i);
92             if (child.getVisibility() != GONE) {
93                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
94 
95                 final int width = child.getMeasuredWidth();
96                 final int height = child.getMeasuredHeight();
97 
98                 int childLeft;
99                 int childTop;
100 
101                 int gravity = lp.gravity;
102                 if (gravity == -1) {
103                     gravity = DEFAULT_CHILD_GRAVITY;
104                 }
105 
106                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
107                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
108 
109                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
110                     case Gravity.CENTER_HORIZONTAL:
111                         childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
112                                 lp.leftMargin - lp.rightMargin;
113                         break;
114                     case Gravity.RIGHT:
115                         childLeft = parentRight - width - lp.rightMargin;
116                         break;
117                     case Gravity.LEFT:
118                     default:
119                         childLeft = parentLeft + lp.leftMargin;
120                 }
121 
122                 switch (verticalGravity) {
123                     case Gravity.TOP:
124                         childTop = parentTop + lp.topMargin;
125                         break;
126                     case Gravity.CENTER_VERTICAL:
127                         childTop = parentTop + (parentBottom - parentTop - height) / 2 +
128                                 lp.topMargin - lp.bottomMargin;
129                         break;
130                     case Gravity.BOTTOM:
131                         childTop = parentBottom - height - lp.bottomMargin;
132                         break;
133                     default:
134                         childTop = parentTop + lp.topMargin;
135                 }
136 
137                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
138                 // synchronize child pivot to be same as ScaleFrameLayout's pivot
139                 child.setPivotX(pivotX - childLeft);
140                 child.setPivotY(pivotY - childTop);
141             }
142         }
143     }
144 
getScaledMeasureSpec(int measureSpec, float scale)145     private static int getScaledMeasureSpec(int measureSpec, float scale) {
146         return scale == 1f ? measureSpec : MeasureSpec.makeMeasureSpec(
147                 (int) (MeasureSpec.getSize(measureSpec) / scale + 0.5f),
148                 MeasureSpec.getMode(measureSpec));
149     }
150 
151     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)152     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
153         if (mLayoutScaleX != 1f || mLayoutScaleY != 1f) {
154             final int scaledWidthMeasureSpec =
155                     getScaledMeasureSpec(widthMeasureSpec, mLayoutScaleX);
156             final int scaledHeightMeasureSpec =
157                     getScaledMeasureSpec(heightMeasureSpec, mLayoutScaleY);
158             super.onMeasure(scaledWidthMeasureSpec, scaledHeightMeasureSpec);
159             setMeasuredDimension((int)(getMeasuredWidth() * mLayoutScaleX + 0.5f),
160                     (int)(getMeasuredHeight() * mLayoutScaleY + 0.5f));
161         } else {
162             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
163         }
164     }
165 
166     /**
167      * setForeground() is not supported,  throws UnsupportedOperationException() when called.
168      */
169     @Override
setForeground(Drawable d)170     public void setForeground(Drawable d) {
171         throw new UnsupportedOperationException();
172     }
173 }
174