| /* |
| * 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 android.support.v17.leanback.widget; |
| |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.support.annotation.ColorInt; |
| import android.support.v17.leanback.R; |
| import android.util.AttributeSet; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.FrameLayout; |
| |
| /** |
| * 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.getInstance().supportsShadow(); |
| } |
| |
| /** |
| * Returns true if the platform sdk supports dynamic shadows. |
| */ |
| public static boolean supportsDynamicShadow() { |
| return ShadowHelper.getInstance().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.getInstance().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.getInstance().addDynamicShadow( |
| this, mUnfocusedZ, mFocusedZ, mRoundedCornerRadius); |
| break; |
| case SHADOW_STATIC: |
| mShadowImpl = StaticShadowHelper.getInstance().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.getInstance().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; |
| } |
| } |