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; 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 * @hide 31 */ 32 @RestrictTo(LIBRARY_GROUP) 33 public class ScaleFrameLayout extends FrameLayout { 34 35 private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START; 36 37 private float mLayoutScaleX = 1f; 38 private float mLayoutScaleY = 1f; 39 40 private float mChildScale = 1f; 41 42 public ScaleFrameLayout(Context context) { 43 this(context ,null); 44 } 45 46 public ScaleFrameLayout(Context context, AttributeSet attrs) { 47 this(context, attrs, 0); 48 } 49 50 public ScaleFrameLayout(Context context, AttributeSet attrs, 51 int defStyle) { 52 super(context, attrs, defStyle); 53 } 54 55 public void setLayoutScaleX(float scaleX) { 56 if (scaleX != mLayoutScaleX) { 57 mLayoutScaleX = scaleX; 58 requestLayout(); 59 } 60 } 61 62 public void setLayoutScaleY(float scaleY) { 63 if (scaleY != mLayoutScaleY) { 64 mLayoutScaleY = scaleY; 65 requestLayout(); 66 } 67 } 68 69 public void setChildScale(float scale) { 70 if (mChildScale != scale) { 71 mChildScale = scale; 72 for (int i = 0; i < getChildCount(); i++) { 73 getChildAt(i).setScaleX(scale); 74 getChildAt(i).setScaleY(scale); 75 } 76 } 77 } 78 79 @Override 80 public void addView(View child, int index, ViewGroup.LayoutParams params) { 81 super.addView(child, index, params); 82 child.setScaleX(mChildScale); 83 child.setScaleY(mChildScale); 84 } 85 86 @Override 87 protected boolean addViewInLayout (View child, int index, ViewGroup.LayoutParams params, 88 boolean preventRequestLayout) { 89 boolean ret = super.addViewInLayout(child, index, params, preventRequestLayout); 90 if (ret) { 91 child.setScaleX(mChildScale); 92 child.setScaleY(mChildScale); 93 } 94 return ret; 95 } 96 97 @Override 98 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 99 final int count = getChildCount(); 100 101 final int parentLeft, parentRight; 102 final int layoutDirection = getLayoutDirection(); 103 final float pivotX = (layoutDirection == View.LAYOUT_DIRECTION_RTL) 104 ? getWidth() - getPivotX() 105 : getPivotX(); 106 if (mLayoutScaleX != 1f) { 107 parentLeft = getPaddingLeft() + (int)(pivotX - pivotX / mLayoutScaleX + 0.5f); 108 parentRight = (int)(pivotX + (right - left - pivotX) / mLayoutScaleX + 0.5f) 109 - getPaddingRight(); 110 } else { 111 parentLeft = getPaddingLeft(); 112 parentRight = right - left - getPaddingRight(); 113 } 114 115 final int parentTop, parentBottom; 116 final float pivotY = getPivotY(); 117 if (mLayoutScaleY != 1f) { 118 parentTop = getPaddingTop() + (int)(pivotY - pivotY / mLayoutScaleY + 0.5f); 119 parentBottom = (int)(pivotY + (bottom - top - pivotY) / mLayoutScaleY + 0.5f) 120 - getPaddingBottom(); 121 } else { 122 parentTop = getPaddingTop(); 123 parentBottom = bottom - top - getPaddingBottom(); 124 } 125 126 for (int i = 0; i < count; i++) { 127 final View child = getChildAt(i); 128 if (child.getVisibility() != GONE) { 129 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 130 131 final int width = child.getMeasuredWidth(); 132 final int height = child.getMeasuredHeight(); 133 134 int childLeft; 135 int childTop; 136 137 int gravity = lp.gravity; 138 if (gravity == -1) { 139 gravity = DEFAULT_CHILD_GRAVITY; 140 } 141 142 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 143 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 144 145 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 146 case Gravity.CENTER_HORIZONTAL: 147 childLeft = parentLeft + (parentRight - parentLeft - width) / 2 148 + lp.leftMargin - lp.rightMargin; 149 break; 150 case Gravity.RIGHT: 151 childLeft = parentRight - width - lp.rightMargin; 152 break; 153 case Gravity.LEFT: 154 default: 155 childLeft = parentLeft + lp.leftMargin; 156 } 157 158 switch (verticalGravity) { 159 case Gravity.TOP: 160 childTop = parentTop + lp.topMargin; 161 break; 162 case Gravity.CENTER_VERTICAL: 163 childTop = parentTop + (parentBottom - parentTop - height) / 2 164 + lp.topMargin - lp.bottomMargin; 165 break; 166 case Gravity.BOTTOM: 167 childTop = parentBottom - height - lp.bottomMargin; 168 break; 169 default: 170 childTop = parentTop + lp.topMargin; 171 } 172 173 child.layout(childLeft, childTop, childLeft + width, childTop + height); 174 // synchronize child pivot to be same as ScaleFrameLayout's pivot 175 child.setPivotX(pivotX - childLeft); 176 child.setPivotY(pivotY - childTop); 177 } 178 } 179 } 180 181 private static int getScaledMeasureSpec(int measureSpec, float scale) { 182 return scale == 1f ? measureSpec : MeasureSpec.makeMeasureSpec( 183 (int) (MeasureSpec.getSize(measureSpec) / scale + 0.5f), 184 MeasureSpec.getMode(measureSpec)); 185 } 186 187 @Override 188 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 189 if (mLayoutScaleX != 1f || mLayoutScaleY != 1f) { 190 final int scaledWidthMeasureSpec = 191 getScaledMeasureSpec(widthMeasureSpec, mLayoutScaleX); 192 final int scaledHeightMeasureSpec = 193 getScaledMeasureSpec(heightMeasureSpec, mLayoutScaleY); 194 super.onMeasure(scaledWidthMeasureSpec, scaledHeightMeasureSpec); 195 setMeasuredDimension((int)(getMeasuredWidth() * mLayoutScaleX + 0.5f), 196 (int)(getMeasuredHeight() * mLayoutScaleY + 0.5f)); 197 } else { 198 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 199 } 200 } 201 202 /** 203 * setForeground() is not supported, throws UnsupportedOperationException() when called. 204 */ 205 @Override 206 public void setForeground(Drawable d) { 207 throw new UnsupportedOperationException(); 208 } 209 210 } 211