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 android.content.Context;
     17 import android.graphics.Canvas;
     18 import android.graphics.Color;
     19 import android.graphics.Paint;
     20 import android.graphics.Rect;
     21 import android.util.AttributeSet;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 import android.widget.FrameLayout;
     25 
     26 import androidx.annotation.ColorInt;
     27 import androidx.leanback.R;
     28 
     29 /**
     30  * Provides an SDK version-independent wrapper to support shadows, color overlays, and rounded
     31  * corners.  It's not always preferred to create a ShadowOverlayContainer, use
     32  * {@link ShadowOverlayHelper} instead.
     33  * <p>
     34  * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
     35  * before using shadow.  Depending on sdk version, optical bounds might be applied
     36  * to parent.
     37  * </p>
     38  * <p>
     39  * If shadows can appear outside the bounds of the parent view, setClipChildren(false) must
     40  * be called on the grandparent view.
     41  * </p>
     42  * <p>
     43  * {@link #initialize(boolean, boolean, boolean)} must be first called on the container.
     44  * Then call {@link #wrap(View)} to insert the wrapped view into the container.
     45  * </p>
     46  * <p>
     47  * Call {@link #setShadowFocusLevel(float)} to control the strength of the shadow (focused shadows
     48  * cast stronger shadows).
     49  * </p>
     50  * <p>
     51  * Call {@link #setOverlayColor(int)} to control overlay color.
     52  * </p>
     53  */
     54 public class ShadowOverlayContainer extends FrameLayout {
     55 
     56     /**
     57      * No shadow.
     58      */
     59     public static final int SHADOW_NONE = ShadowOverlayHelper.SHADOW_NONE;
     60 
     61     /**
     62      * Shadows are fixed.
     63      */
     64     public static final int SHADOW_STATIC = ShadowOverlayHelper.SHADOW_STATIC;
     65 
     66     /**
     67      * Shadows depend on the size, shape, and position of the view.
     68      */
     69     public static final int SHADOW_DYNAMIC = ShadowOverlayHelper.SHADOW_DYNAMIC;
     70 
     71     private boolean mInitialized;
     72     private Object mShadowImpl;
     73     private View mWrappedView;
     74     private boolean mRoundedCorners;
     75     private int mShadowType = SHADOW_NONE;
     76     private float mUnfocusedZ;
     77     private float mFocusedZ;
     78     private int mRoundedCornerRadius;
     79     private static final Rect sTempRect = new Rect();
     80     private Paint mOverlayPaint;
     81     int mOverlayColor;
     82 
     83     /**
     84      * Create ShadowOverlayContainer and auto select shadow type.
     85      */
     86     public ShadowOverlayContainer(Context context) {
     87         this(context, null, 0);
     88     }
     89 
     90     /**
     91      * Create ShadowOverlayContainer and auto select shadow type.
     92      */
     93     public ShadowOverlayContainer(Context context, AttributeSet attrs) {
     94         this(context, attrs, 0);
     95     }
     96 
     97     /**
     98      * Create ShadowOverlayContainer and auto select shadow type.
     99      */
    100     public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) {
    101         super(context, attrs, defStyle);
    102         useStaticShadow();
    103         useDynamicShadow();
    104     }
    105 
    106     /**
    107      * Create ShadowOverlayContainer with specific shadowType.
    108      */
    109     ShadowOverlayContainer(Context context,
    110             int shadowType, boolean hasColorDimOverlay,
    111             float unfocusedZ, float focusedZ, int roundedCornerRadius) {
    112         super(context);
    113         mUnfocusedZ = unfocusedZ;
    114         mFocusedZ = focusedZ;
    115         initialize(shadowType, hasColorDimOverlay, roundedCornerRadius);
    116     }
    117 
    118     /**
    119      * Return true if the platform sdk supports shadow.
    120      */
    121     public static boolean supportsShadow() {
    122         return StaticShadowHelper.supportsShadow();
    123     }
    124 
    125     /**
    126      * Returns true if the platform sdk supports dynamic shadows.
    127      */
    128     public static boolean supportsDynamicShadow() {
    129         return ShadowHelper.supportsDynamicShadow();
    130     }
    131 
    132     /**
    133      * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
    134      * before using shadow.  Depending on sdk version, optical bounds might be applied
    135      * to parent.
    136      */
    137     public static void prepareParentForShadow(ViewGroup parent) {
    138         StaticShadowHelper.prepareParent(parent);
    139     }
    140 
    141     /**
    142      * Sets the shadow type to {@link #SHADOW_DYNAMIC} if supported.
    143      */
    144     public void useDynamicShadow() {
    145         useDynamicShadow(getResources().getDimension(R.dimen.lb_material_shadow_normal_z),
    146                 getResources().getDimension(R.dimen.lb_material_shadow_focused_z));
    147     }
    148 
    149     /**
    150      * Sets the shadow type to {@link #SHADOW_DYNAMIC} if supported and sets the elevation/Z
    151      * values to the given parameters.
    152      */
    153     public void useDynamicShadow(float unfocusedZ, float focusedZ) {
    154         if (mInitialized) {
    155             throw new IllegalStateException("Already initialized");
    156         }
    157         if (supportsDynamicShadow()) {
    158             mShadowType = SHADOW_DYNAMIC;
    159             mUnfocusedZ = unfocusedZ;
    160             mFocusedZ = focusedZ;
    161         }
    162     }
    163 
    164     /**
    165      * Sets the shadow type to {@link #SHADOW_STATIC} if supported.
    166      */
    167     public void useStaticShadow() {
    168         if (mInitialized) {
    169             throw new IllegalStateException("Already initialized");
    170         }
    171         if (supportsShadow()) {
    172             mShadowType = SHADOW_STATIC;
    173         }
    174     }
    175 
    176     /**
    177      * Returns the shadow type, one of {@link #SHADOW_NONE}, {@link #SHADOW_STATIC}, or
    178      * {@link #SHADOW_DYNAMIC}.
    179      */
    180     public int getShadowType() {
    181         return mShadowType;
    182     }
    183 
    184     /**
    185      * Initialize shadows, color overlay.
    186      * @deprecated use {@link ShadowOverlayHelper#createShadowOverlayContainer(Context)} instead.
    187      */
    188     @Deprecated
    189     public void initialize(boolean hasShadow, boolean hasColorDimOverlay) {
    190         initialize(hasShadow, hasColorDimOverlay, true);
    191     }
    192 
    193     /**
    194      * Initialize shadows, color overlay, and rounded corners.  All are optional.
    195      * Shadow type are auto-selected based on {@link #useStaticShadow()} and
    196      * {@link #useDynamicShadow()} call.
    197      * @deprecated use {@link ShadowOverlayHelper#createShadowOverlayContainer(Context)} instead.
    198      */
    199     @Deprecated
    200     public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) {
    201         int shadowType;
    202         if (!hasShadow) {
    203             shadowType = SHADOW_NONE;
    204         } else {
    205             shadowType = mShadowType;
    206         }
    207         int roundedCornerRadius = roundedCorners ? getContext().getResources().getDimensionPixelSize(
    208                 R.dimen.lb_rounded_rect_corner_radius) : 0;
    209         initialize(shadowType, hasColorDimOverlay, roundedCornerRadius);
    210     }
    211 
    212     /**
    213      * Initialize shadows, color overlay, and rounded corners.  All are optional.
    214      */
    215     void initialize(int shadowType, boolean hasColorDimOverlay, int roundedCornerRadius) {
    216         if (mInitialized) {
    217             throw new IllegalStateException();
    218         }
    219         mInitialized = true;
    220         mRoundedCornerRadius = roundedCornerRadius;
    221         mRoundedCorners = roundedCornerRadius > 0;
    222         mShadowType = shadowType;
    223         switch (mShadowType) {
    224             case SHADOW_DYNAMIC:
    225                 mShadowImpl = ShadowHelper.addDynamicShadow(
    226                         this, mUnfocusedZ, mFocusedZ, mRoundedCornerRadius);
    227                 break;
    228             case SHADOW_STATIC:
    229                 mShadowImpl = StaticShadowHelper.addStaticShadow(this);
    230                 break;
    231         }
    232         if (hasColorDimOverlay) {
    233             setWillNotDraw(false);
    234             mOverlayColor = Color.TRANSPARENT;
    235             mOverlayPaint = new Paint();
    236             mOverlayPaint.setColor(mOverlayColor);
    237             mOverlayPaint.setStyle(Paint.Style.FILL);
    238         } else {
    239             setWillNotDraw(true);
    240             mOverlayPaint = null;
    241         }
    242     }
    243 
    244     @Override
    245     public void draw(Canvas canvas) {
    246         super.draw(canvas);
    247         if (mOverlayPaint != null && mOverlayColor != Color.TRANSPARENT) {
    248             canvas.drawRect(mWrappedView.getLeft(), mWrappedView.getTop(),
    249                     mWrappedView.getRight(), mWrappedView.getBottom(),
    250                     mOverlayPaint);
    251         }
    252     }
    253 
    254     /**
    255      * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
    256      */
    257     public void setShadowFocusLevel(float level) {
    258         if (mShadowImpl != null) {
    259             ShadowOverlayHelper.setShadowFocusLevel(mShadowImpl, mShadowType, level);
    260         }
    261     }
    262 
    263     /**
    264      * Set color (with alpha) of the overlay.
    265      */
    266     public void setOverlayColor(@ColorInt int overlayColor) {
    267         if (mOverlayPaint != null) {
    268             if (overlayColor != mOverlayColor) {
    269                 mOverlayColor = overlayColor;
    270                 mOverlayPaint.setColor(overlayColor);
    271                 invalidate();
    272             }
    273         }
    274     }
    275 
    276     /**
    277      * Inserts view into the wrapper.
    278      */
    279     public void wrap(View view) {
    280         if (!mInitialized || mWrappedView != null) {
    281             throw new IllegalStateException();
    282         }
    283         ViewGroup.LayoutParams lp = view.getLayoutParams();
    284         if (lp != null) {
    285             // if wrapped view has layout params, inherit everything but width/height.
    286             // Wrapped view is assigned a FrameLayout.LayoutParams with width and height only.
    287             // Margins, etc are assigned to the wrapper and take effect in parent container.
    288             ViewGroup.LayoutParams wrapped_lp = new FrameLayout.LayoutParams(lp.width, lp.height);
    289             // Uses MATCH_PARENT for MATCH_PARENT, WRAP_CONTENT for WRAP_CONTENT and fixed size,
    290             // App can still change wrapped view fixed width/height afterwards.
    291             lp.width = lp.width == LayoutParams.MATCH_PARENT
    292                     ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT;
    293             lp.height = lp.height == LayoutParams.MATCH_PARENT
    294                     ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT;
    295             this.setLayoutParams(lp);
    296             addView(view, wrapped_lp);
    297         } else {
    298             addView(view);
    299         }
    300         if (mRoundedCorners && mShadowType != SHADOW_DYNAMIC) {
    301             RoundedRectHelper.setClipToRoundedOutline(this, true);
    302         }
    303         mWrappedView = view;
    304     }
    305 
    306     /**
    307      * Returns the wrapper view.
    308      */
    309     public View getWrappedView() {
    310         return mWrappedView;
    311     }
    312 
    313     @Override
    314     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    315         super.onLayout(changed, l, t, r, b);
    316         if (changed && mWrappedView != null) {
    317             sTempRect.left = (int) mWrappedView.getPivotX();
    318             sTempRect.top = (int) mWrappedView.getPivotY();
    319             offsetDescendantRectToMyCoords(mWrappedView, sTempRect);
    320             setPivotX(sTempRect.left);
    321             setPivotY(sTempRect.top);
    322         }
    323     }
    324 
    325     @Override
    326     public boolean hasOverlappingRendering() {
    327         return false;
    328     }
    329 }
    330