Home | History | Annotate | Download | only in widget
      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