blob: 2feb5c59e3abda443028f5d8f1b1433db67d0040 [file] [log] [blame]
/*
* Copyright (C) 2015 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.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v17.leanback.R;
import android.support.v17.leanback.system.Settings;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.View;
/**
* Helper for shadow.
* @hide
*/
public final class ShadowOverlayHelper {
/**
* 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;
int mShadowType = SHADOW_NONE;
boolean mNeedsOverlay;
boolean mNeedsRoundedCorner;
boolean mNeedsShadow;
boolean mNeedsWrapper;
private ItemBridgeAdapter.Wrapper mCardWrapper;
float mUnfocusedZ;
float mFocusedZ;
/**
* 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();
}
/**
* Returns true if the platform sdk supports rounded corner through outline.
*/
public static boolean supportsRoundedCorner() {
return RoundedRectHelper.supportsRoundedCorner();
}
/**
* Returns true if view.setForeground() is supported.
*/
public static boolean supportsForeground() {
return ForegroundHelper.supportsForeground();
}
/**
* Create ShadowHelper that includes all options.
*
* @param context Context that required to query options
* @param needsOverlay true if overlay (dim) is needed
* @param needsShadow true if shadow is needed
* @param needsRoundedCorner true if roundedCorner is needed.
* @param preferZOrder true if prefer dynamic shadow otherwise static shadow is used.
*/
public ShadowOverlayHelper(Context context,
boolean needsOverlay, boolean needsShadow, boolean needsRoundedCorner,
boolean preferZOrder) {
mNeedsOverlay = needsOverlay;
mNeedsRoundedCorner = needsRoundedCorner && supportsRoundedCorner();
mNeedsShadow = needsShadow && supportsShadow();
// Force to use wrapper to avoid rebuildOutline() on animating foreground drawable.
// See b/22724385
final boolean forceWrapperForOverlay = false;
// figure out shadow type and if we need use wrapper:
if (mNeedsShadow) {
// if static shadow is prefered or dynamic shadow is not supported,
// use static shadow, otherwise use dynamic shadow.
if (!preferZOrder || !supportsDynamicShadow()) {
mShadowType = SHADOW_STATIC;
// static shadow requires ShadowOverlayContainer to support crossfading
// of two shadow views.
mNeedsWrapper = true;
} else {
useDynamicShadow(context);
mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay);
}
} else {
mShadowType = SHADOW_NONE;
mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay);
}
if (mNeedsWrapper) {
mCardWrapper = new ItemBridgeAdapter.Wrapper() {
@Override
public View createWrapper(View root) {
Context context = root.getContext();
ShadowOverlayContainer wrapper = createShadowOverlayContainer(context);
wrapper.setLayoutParams(
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
return wrapper;
}
@Override
public void wrap(View wrapper, View wrapped) {
((ShadowOverlayContainer) wrapper).wrap(wrapped);
}
};
}
}
/**
* {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
* before using shadow. Depending on Shadow type, optical bounds might be applied.
*/
public void prepareParentForShadow(ViewGroup parent) {
if (mShadowType == SHADOW_STATIC) {
StaticShadowHelper.getInstance().prepareParent(parent);
}
}
void useDynamicShadow(Context context) {
Resources res = context.getResources();
useDynamicShadow(res.getDimension(R.dimen.lb_material_shadow_normal_z),
res.getDimension(R.dimen.lb_material_shadow_focused_z));
}
void useDynamicShadow(float unfocusedZ, float focusedZ) {
mShadowType = SHADOW_DYNAMIC;
mUnfocusedZ = unfocusedZ;
mFocusedZ = focusedZ;
}
public int getShadowType() {
return mShadowType;
}
public boolean needsOverlay() {
return mNeedsOverlay;
}
public boolean needsRoundedCorner() {
return mNeedsRoundedCorner;
}
ShadowOverlayContainer createShadowOverlayContainer(Context context) {
return new ShadowOverlayContainer(context, mShadowType, mNeedsOverlay,
mNeedsRoundedCorner);
}
public ItemBridgeAdapter.Wrapper getWrapper() {
return mCardWrapper;
}
public void setOverlayColor(View view, int color) {
if (mNeedsWrapper) {
((ShadowOverlayContainer) view).setOverlayColor(color);
} else {
Drawable d = ForegroundHelper.getInstance().getForeground(view);
if (d instanceof ColorDrawable) {
((ColorDrawable) d).setColor(color);
} else {
ForegroundHelper.getInstance().setForeground(view, new ColorDrawable(color));
}
}
}
/**
* Called on view is created.
* @param view
*/
public void onViewCreated(View view) {
if (!mNeedsWrapper) {
if (!mNeedsShadow) {
if (mNeedsRoundedCorner) {
RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
}
} else {
if (mShadowType == SHADOW_DYNAMIC) {
Object tag = ShadowHelper.getInstance().addDynamicShadow(
view, mUnfocusedZ, mFocusedZ, mNeedsRoundedCorner);
view.setTag(R.id.lb_shadow_impl, tag);
}
}
}
}
public static Object getNoneWrapperDyamicShadowImpl(View view) {
return view.getTag(R.id.lb_shadow_impl);
}
/**
* Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
*/
public static void setShadowFocusLevel(View view, float level) {
if (view instanceof ShadowOverlayContainer) {
((ShadowOverlayContainer) view).setShadowFocusLevel(level);
} else {
setShadowFocusLevel(getNoneWrapperDyamicShadowImpl(view), SHADOW_DYNAMIC, level);
}
}
static void setShadowFocusLevel(Object impl, int shadowType, float level) {
if (impl != null) {
if (level < 0f) {
level = 0f;
} else if (level > 1f) {
level = 1f;
}
switch (shadowType) {
case SHADOW_DYNAMIC:
ShadowHelper.getInstance().setShadowFocusLevel(impl, level);
break;
case SHADOW_STATIC:
StaticShadowHelper.getInstance().setShadowFocusLevel(impl, level);
break;
}
}
}
}