|  | /* | 
|  | * Copyright (C) 2014 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | 
|  | * in compliance with the License. You may obtain a copy of the License at | 
|  | * | 
|  | * http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software distributed under the License | 
|  | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | 
|  | * or implied. See the License for the specific language governing permissions and limitations under | 
|  | * the License. | 
|  | */ | 
|  | package androidx.leanback.widget; | 
|  |  | 
|  | import android.content.Context; | 
|  | import android.graphics.Canvas; | 
|  | import android.graphics.Color; | 
|  | import android.graphics.Paint; | 
|  | import android.graphics.Rect; | 
|  | import android.util.AttributeSet; | 
|  | import android.view.View; | 
|  | import android.view.ViewGroup; | 
|  | import android.widget.FrameLayout; | 
|  |  | 
|  | import androidx.annotation.ColorInt; | 
|  | import androidx.leanback.R; | 
|  |  | 
|  | /** | 
|  | * Provides an SDK version-independent wrapper to support shadows, color overlays, and rounded | 
|  | * corners.  It's not always preferred to create a ShadowOverlayContainer, use | 
|  | * {@link ShadowOverlayHelper} instead. | 
|  | * <p> | 
|  | * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container | 
|  | * before using shadow.  Depending on sdk version, optical bounds might be applied | 
|  | * to parent. | 
|  | * </p> | 
|  | * <p> | 
|  | * If shadows can appear outside the bounds of the parent view, setClipChildren(false) must | 
|  | * be called on the grandparent view. | 
|  | * </p> | 
|  | * <p> | 
|  | * {@link #initialize(boolean, boolean, boolean)} must be first called on the container. | 
|  | * Then call {@link #wrap(View)} to insert the wrapped view into the container. | 
|  | * </p> | 
|  | * <p> | 
|  | * Call {@link #setShadowFocusLevel(float)} to control the strength of the shadow (focused shadows | 
|  | * cast stronger shadows). | 
|  | * </p> | 
|  | * <p> | 
|  | * Call {@link #setOverlayColor(int)} to control overlay color. | 
|  | * </p> | 
|  | */ | 
|  | public class ShadowOverlayContainer extends FrameLayout { | 
|  |  | 
|  | /** | 
|  | * No shadow. | 
|  | */ | 
|  | public static final int SHADOW_NONE = ShadowOverlayHelper.SHADOW_NONE; | 
|  |  | 
|  | /** | 
|  | * Shadows are fixed. | 
|  | */ | 
|  | public static final int SHADOW_STATIC = ShadowOverlayHelper.SHADOW_STATIC; | 
|  |  | 
|  | /** | 
|  | * Shadows depend on the size, shape, and position of the view. | 
|  | */ | 
|  | public static final int SHADOW_DYNAMIC = ShadowOverlayHelper.SHADOW_DYNAMIC; | 
|  |  | 
|  | private boolean mInitialized; | 
|  | private Object mShadowImpl; | 
|  | private View mWrappedView; | 
|  | private boolean mRoundedCorners; | 
|  | private int mShadowType = SHADOW_NONE; | 
|  | private float mUnfocusedZ; | 
|  | private float mFocusedZ; | 
|  | private int mRoundedCornerRadius; | 
|  | private static final Rect sTempRect = new Rect(); | 
|  | private Paint mOverlayPaint; | 
|  | int mOverlayColor; | 
|  |  | 
|  | /** | 
|  | * Create ShadowOverlayContainer and auto select shadow type. | 
|  | */ | 
|  | public ShadowOverlayContainer(Context context) { | 
|  | this(context, null, 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create ShadowOverlayContainer and auto select shadow type. | 
|  | */ | 
|  | public ShadowOverlayContainer(Context context, AttributeSet attrs) { | 
|  | this(context, attrs, 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create ShadowOverlayContainer and auto select shadow type. | 
|  | */ | 
|  | public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) { | 
|  | super(context, attrs, defStyle); | 
|  | useStaticShadow(); | 
|  | useDynamicShadow(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create ShadowOverlayContainer with specific shadowType. | 
|  | */ | 
|  | ShadowOverlayContainer(Context context, | 
|  | int shadowType, boolean hasColorDimOverlay, | 
|  | float unfocusedZ, float focusedZ, int roundedCornerRadius) { | 
|  | super(context); | 
|  | mUnfocusedZ = unfocusedZ; | 
|  | mFocusedZ = focusedZ; | 
|  | initialize(shadowType, hasColorDimOverlay, roundedCornerRadius); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return true if the platform sdk supports shadow. | 
|  | */ | 
|  | public static boolean supportsShadow() { | 
|  | return StaticShadowHelper.supportsShadow(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if the platform sdk supports dynamic shadows. | 
|  | */ | 
|  | public static boolean supportsDynamicShadow() { | 
|  | return ShadowHelper.supportsDynamicShadow(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container | 
|  | * before using shadow.  Depending on sdk version, optical bounds might be applied | 
|  | * to parent. | 
|  | */ | 
|  | public static void prepareParentForShadow(ViewGroup parent) { | 
|  | StaticShadowHelper.prepareParent(parent); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the shadow type to {@link #SHADOW_DYNAMIC} if supported. | 
|  | */ | 
|  | public void useDynamicShadow() { | 
|  | useDynamicShadow(getResources().getDimension(R.dimen.lb_material_shadow_normal_z), | 
|  | getResources().getDimension(R.dimen.lb_material_shadow_focused_z)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the shadow type to {@link #SHADOW_DYNAMIC} if supported and sets the elevation/Z | 
|  | * values to the given parameters. | 
|  | */ | 
|  | public void useDynamicShadow(float unfocusedZ, float focusedZ) { | 
|  | if (mInitialized) { | 
|  | throw new IllegalStateException("Already initialized"); | 
|  | } | 
|  | if (supportsDynamicShadow()) { | 
|  | mShadowType = SHADOW_DYNAMIC; | 
|  | mUnfocusedZ = unfocusedZ; | 
|  | mFocusedZ = focusedZ; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the shadow type to {@link #SHADOW_STATIC} if supported. | 
|  | */ | 
|  | public void useStaticShadow() { | 
|  | if (mInitialized) { | 
|  | throw new IllegalStateException("Already initialized"); | 
|  | } | 
|  | if (supportsShadow()) { | 
|  | mShadowType = SHADOW_STATIC; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the shadow type, one of {@link #SHADOW_NONE}, {@link #SHADOW_STATIC}, or | 
|  | * {@link #SHADOW_DYNAMIC}. | 
|  | */ | 
|  | public int getShadowType() { | 
|  | return mShadowType; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialize shadows, color overlay. | 
|  | * @deprecated use {@link ShadowOverlayHelper#createShadowOverlayContainer(Context)} instead. | 
|  | */ | 
|  | @Deprecated | 
|  | public void initialize(boolean hasShadow, boolean hasColorDimOverlay) { | 
|  | initialize(hasShadow, hasColorDimOverlay, true); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialize shadows, color overlay, and rounded corners.  All are optional. | 
|  | * Shadow type are auto-selected based on {@link #useStaticShadow()} and | 
|  | * {@link #useDynamicShadow()} call. | 
|  | * @deprecated use {@link ShadowOverlayHelper#createShadowOverlayContainer(Context)} instead. | 
|  | */ | 
|  | @Deprecated | 
|  | public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) { | 
|  | int shadowType; | 
|  | if (!hasShadow) { | 
|  | shadowType = SHADOW_NONE; | 
|  | } else { | 
|  | shadowType = mShadowType; | 
|  | } | 
|  | int roundedCornerRadius = roundedCorners ? getContext().getResources().getDimensionPixelSize( | 
|  | R.dimen.lb_rounded_rect_corner_radius) : 0; | 
|  | initialize(shadowType, hasColorDimOverlay, roundedCornerRadius); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialize shadows, color overlay, and rounded corners.  All are optional. | 
|  | */ | 
|  | void initialize(int shadowType, boolean hasColorDimOverlay, int roundedCornerRadius) { | 
|  | if (mInitialized) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  | mInitialized = true; | 
|  | mRoundedCornerRadius = roundedCornerRadius; | 
|  | mRoundedCorners = roundedCornerRadius > 0; | 
|  | mShadowType = shadowType; | 
|  | switch (mShadowType) { | 
|  | case SHADOW_DYNAMIC: | 
|  | mShadowImpl = ShadowHelper.addDynamicShadow( | 
|  | this, mUnfocusedZ, mFocusedZ, mRoundedCornerRadius); | 
|  | break; | 
|  | case SHADOW_STATIC: | 
|  | mShadowImpl = StaticShadowHelper.addStaticShadow(this); | 
|  | break; | 
|  | } | 
|  | if (hasColorDimOverlay) { | 
|  | setWillNotDraw(false); | 
|  | mOverlayColor = Color.TRANSPARENT; | 
|  | mOverlayPaint = new Paint(); | 
|  | mOverlayPaint.setColor(mOverlayColor); | 
|  | mOverlayPaint.setStyle(Paint.Style.FILL); | 
|  | } else { | 
|  | setWillNotDraw(true); | 
|  | mOverlayPaint = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void draw(Canvas canvas) { | 
|  | super.draw(canvas); | 
|  | if (mOverlayPaint != null && mOverlayColor != Color.TRANSPARENT) { | 
|  | canvas.drawRect(mWrappedView.getLeft(), mWrappedView.getTop(), | 
|  | mWrappedView.getRight(), mWrappedView.getBottom(), | 
|  | mOverlayPaint); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused. | 
|  | */ | 
|  | public void setShadowFocusLevel(float level) { | 
|  | if (mShadowImpl != null) { | 
|  | ShadowOverlayHelper.setShadowFocusLevel(mShadowImpl, mShadowType, level); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set color (with alpha) of the overlay. | 
|  | */ | 
|  | public void setOverlayColor(@ColorInt int overlayColor) { | 
|  | if (mOverlayPaint != null) { | 
|  | if (overlayColor != mOverlayColor) { | 
|  | mOverlayColor = overlayColor; | 
|  | mOverlayPaint.setColor(overlayColor); | 
|  | invalidate(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Inserts view into the wrapper. | 
|  | */ | 
|  | public void wrap(View view) { | 
|  | if (!mInitialized || mWrappedView != null) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  | ViewGroup.LayoutParams lp = view.getLayoutParams(); | 
|  | if (lp != null) { | 
|  | // if wrapped view has layout params, inherit everything but width/height. | 
|  | // Wrapped view is assigned a FrameLayout.LayoutParams with width and height only. | 
|  | // Margins, etc are assigned to the wrapper and take effect in parent container. | 
|  | ViewGroup.LayoutParams wrapped_lp = new FrameLayout.LayoutParams(lp.width, lp.height); | 
|  | // Uses MATCH_PARENT for MATCH_PARENT, WRAP_CONTENT for WRAP_CONTENT and fixed size, | 
|  | // App can still change wrapped view fixed width/height afterwards. | 
|  | lp.width = lp.width == LayoutParams.MATCH_PARENT | 
|  | ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT; | 
|  | lp.height = lp.height == LayoutParams.MATCH_PARENT | 
|  | ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT; | 
|  | this.setLayoutParams(lp); | 
|  | addView(view, wrapped_lp); | 
|  | } else { | 
|  | addView(view); | 
|  | } | 
|  | if (mRoundedCorners && mShadowType != SHADOW_DYNAMIC) { | 
|  | RoundedRectHelper.setClipToRoundedOutline(this, true); | 
|  | } | 
|  | mWrappedView = view; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the wrapper view. | 
|  | */ | 
|  | public View getWrappedView() { | 
|  | return mWrappedView; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onLayout(boolean changed, int l, int t, int r, int b) { | 
|  | super.onLayout(changed, l, t, r, b); | 
|  | if (changed && mWrappedView != null) { | 
|  | sTempRect.left = (int) mWrappedView.getPivotX(); | 
|  | sTempRect.top = (int) mWrappedView.getPivotY(); | 
|  | offsetDescendantRectToMyCoords(mWrappedView, sTempRect); | 
|  | setPivotX(sTempRect.left); | 
|  | setPivotY(sTempRect.top); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean hasOverlappingRendering() { | 
|  | return false; | 
|  | } | 
|  | } |