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 androidx.leanback.widget;
15 
16 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
17 
18 import android.content.Context;
19 import android.graphics.drawable.Drawable;
20 import android.util.AttributeSet;
21 import android.view.Gravity;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.FrameLayout;
25 
26 import androidx.annotation.RestrictTo;
27 
28 /**
29  * Subclass of FrameLayout that support scale layout area size for children.
30  */
31 @RestrictTo(LIBRARY_GROUP_PREFIX)
32 public 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 
ScaleFrameLayout(Context context)41     public ScaleFrameLayout(Context context) {
42         this(context ,null);
43     }
44 
ScaleFrameLayout(Context context, AttributeSet attrs)45     public ScaleFrameLayout(Context context, AttributeSet attrs) {
46         this(context, attrs, 0);
47     }
48 
ScaleFrameLayout(Context context, AttributeSet attrs, int defStyle)49     public ScaleFrameLayout(Context context, AttributeSet attrs,
50             int defStyle) {
51         super(context, attrs, defStyle);
52     }
53 
setLayoutScaleX(float scaleX)54     public void setLayoutScaleX(float scaleX) {
55         if (scaleX != mLayoutScaleX) {
56             mLayoutScaleX = scaleX;
57             requestLayout();
58         }
59     }
60 
setLayoutScaleY(float scaleY)61     public void setLayoutScaleY(float scaleY) {
62         if (scaleY != mLayoutScaleY) {
63             mLayoutScaleY = scaleY;
64             requestLayout();
65         }
66     }
67 
setChildScale(float scale)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
addView(View child, int index, ViewGroup.LayoutParams params)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
addViewInLayout(View child, int index, ViewGroup.LayoutParams params, boolean preventRequestLayout)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
onLayout(boolean changed, int left, int top, int right, int bottom)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 
getScaledMeasureSpec(int measureSpec, float scale)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
setForeground(Drawable d)205     public void setForeground(Drawable d) {
206         throw new UnsupportedOperationException();
207     }
208 
209 }
210