blob: e367494dc7bedf62fde8298a633e00f4ec470faf [file] [log] [blame]
/*
* 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.support.annotation.ColorInt;
import android.support.v17.leanback.R;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.graphics.Rect;
/**
* Provides an SDK version-independent wrapper to support shadows, color overlays, and rounded
* corners.
* <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 ViewGroup {
/**
* No shadow.
*/
public static final int SHADOW_NONE = 1;
/**
* Shadows are fixed.
*/
public static final int SHADOW_STATIC = 2;
/**
* Shadows depend on the size, shape, and position of the view.
*/
public static final int SHADOW_DYNAMIC = 3;
private boolean mInitialized;
private View mColorDimOverlay;
private Object mShadowImpl;
private View mWrappedView;
private boolean mRoundedCorners;
private int mShadowType = SHADOW_NONE;
private float mUnfocusedZ;
private float mFocusedZ;
private static final Rect sTempRect = new Rect();
public ShadowOverlayContainer(Context context) {
this(context, null, 0);
}
public ShadowOverlayContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
useStaticShadow();
useDynamicShadow();
}
/**
* 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 parameteres.
*/
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 #initialize(boolean, boolean, boolean)} instead.
*/
@Deprecated
public void initialize(boolean hasShadow, boolean hasColorDimOverlay) {
initialize(hasShadow, hasColorDimOverlay, true);
}
/**
* Initialize shadows, color overlay, and rounded corners. All are optional.
*/
public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) {
if (mInitialized) {
throw new IllegalStateException();
}
mInitialized = true;
if (hasShadow) {
switch (mShadowType) {
case SHADOW_DYNAMIC:
mShadowImpl = ShadowHelper.getInstance().addDynamicShadow(
this, mUnfocusedZ, mFocusedZ, roundedCorners);
break;
case SHADOW_STATIC:
mShadowImpl = StaticShadowHelper.getInstance().addStaticShadow(
this, roundedCorners);
break;
}
}
mRoundedCorners = roundedCorners;
if (hasColorDimOverlay) {
mColorDimOverlay = LayoutInflater.from(getContext())
.inflate(R.layout.lb_card_color_overlay, this, false);
addView(mColorDimOverlay);
}
}
/**
* Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
*/
public void setShadowFocusLevel(float level) {
if (mShadowImpl != null) {
if (level < 0f) {
level = 0f;
} else if (level > 1f) {
level = 1f;
}
switch (mShadowType) {
case SHADOW_DYNAMIC:
ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
break;
case SHADOW_STATIC:
StaticShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
break;
}
}
}
/**
* Set color (with alpha) of the overlay.
*/
public void setOverlayColor(@ColorInt int overlayColor) {
if (mColorDimOverlay != null) {
mColorDimOverlay.setBackgroundColor(overlayColor);
}
}
/**
* Inserts view into the wrapper.
*/
public void wrap(View view) {
if (!mInitialized || mWrappedView != null) {
throw new IllegalStateException();
}
if (mColorDimOverlay != null) {
addView(view, indexOfChild(mColorDimOverlay));
} else {
addView(view);
}
mWrappedView = view;
if (mRoundedCorners) {
RoundedRectHelper.getInstance().setClipToRoundedOutline(mWrappedView, true);
}
}
/**
* Returns the wrapper view.
*/
public View getWrappedView() {
return mWrappedView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mWrappedView == null) {
throw new IllegalStateException();
}
// padding and child margin are not supported.
// first measure the wrapped view, then measure the shadow view and/or overlay view.
int childWidthMeasureSpec, childHeightMeasureSpec;
LayoutParams lp = mWrappedView.getLayoutParams();
if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
}
if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
}
mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
int measuredWidth = mWrappedView.getMeasuredWidth();
int measuredHeight = mWrappedView.getMeasuredHeight();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child == mWrappedView) {
continue;
}
lp = child.getLayoutParams();
if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
(measuredWidth, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
}
if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
(measuredHeight, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
child.layout(0, 0, width, height);
}
}
if (mWrappedView != null) {
sTempRect.left = (int) mWrappedView.getPivotX();
sTempRect.top = (int) mWrappedView.getPivotY();
offsetDescendantRectToMyCoords(mWrappedView, sTempRect);
setPivotX(sTempRect.left);
setPivotY(sTempRect.top);
}
}
}