ShadowOverlayHelper.java revision 20921beeb51f39558a7ac3b646cce8e342cb7183
1/* 2 * Copyright (C) 2015 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 */ 14package android.support.v17.leanback.widget; 15 16import android.content.Context; 17import android.content.res.Resources; 18import android.graphics.drawable.ColorDrawable; 19import android.graphics.drawable.Drawable; 20import android.os.Build; 21import android.support.v17.leanback.R; 22import android.support.v17.leanback.system.Settings; 23import android.util.AttributeSet; 24import android.view.ViewGroup; 25import android.view.ViewGroup.LayoutParams; 26import android.view.View; 27 28 29/** 30 * Helper for shadow. 31 * @hide 32 */ 33public final class ShadowOverlayHelper { 34 35 /** 36 * No shadow. 37 */ 38 public static final int SHADOW_NONE = 1; 39 40 /** 41 * Shadows are fixed. 42 */ 43 public static final int SHADOW_STATIC = 2; 44 45 /** 46 * Shadows depend on the size, shape, and position of the view. 47 */ 48 public static final int SHADOW_DYNAMIC = 3; 49 50 int mShadowType = SHADOW_NONE; 51 boolean mNeedsOverlay; 52 boolean mNeedsRoundedCorner; 53 boolean mNeedsShadow; 54 boolean mNeedsWrapper; 55 private ItemBridgeAdapter.Wrapper mCardWrapper; 56 57 float mUnfocusedZ; 58 float mFocusedZ; 59 60 /** 61 * Return true if the platform sdk supports shadow. 62 */ 63 public static boolean supportsShadow() { 64 return StaticShadowHelper.getInstance().supportsShadow(); 65 } 66 67 /** 68 * Returns true if the platform sdk supports dynamic shadows. 69 */ 70 public static boolean supportsDynamicShadow() { 71 return ShadowHelper.getInstance().supportsDynamicShadow(); 72 } 73 74 /** 75 * Returns true if the platform sdk supports rounded corner through outline. 76 */ 77 public static boolean supportsRoundedCorner() { 78 return RoundedRectHelper.supportsRoundedCorner(); 79 } 80 81 /** 82 * Returns true if view.setForeground() is supported. 83 */ 84 public static boolean supportsForeground() { 85 return ForegroundHelper.supportsForeground(); 86 } 87 88 /** 89 * Create ShadowHelper that includes all options. 90 * 91 * @param context Context that required to query options 92 * @param needsOverlay true if overlay (dim) is needed 93 * @param needsShadow true if shadow is needed 94 * @param needsRoundedCorner true if roundedCorner is needed. 95 * @param preferZOrder true if prefer dynamic shadow otherwise static shadow is used. 96 */ 97 public ShadowOverlayHelper(Context context, 98 boolean needsOverlay, boolean needsShadow, boolean needsRoundedCorner, 99 boolean preferZOrder) { 100 mNeedsOverlay = needsOverlay; 101 mNeedsRoundedCorner = needsRoundedCorner && supportsRoundedCorner(); 102 mNeedsShadow = needsShadow && supportsShadow(); 103 104 // Force to use wrapper to avoid rebuildOutline() on animating foreground drawable. 105 // See b/22724385 106 final boolean forceWrapperForOverlay = true; 107 108 // figure out shadow type and if we need use wrapper: 109 if (mNeedsShadow) { 110 // if static shadow is prefered or dynamic shadow is not supported, 111 // use static shadow, otherwise use dynamic shadow. 112 if (!preferZOrder || !supportsDynamicShadow()) { 113 mShadowType = SHADOW_STATIC; 114 // static shadow requires ShadowOverlayContainer to support crossfading 115 // of two shadow views. 116 mNeedsWrapper = true; 117 } else { 118 useDynamicShadow(context); 119 mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay); 120 } 121 } else { 122 mShadowType = SHADOW_NONE; 123 mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay); 124 } 125 126 if (mNeedsWrapper) { 127 mCardWrapper = new ItemBridgeAdapter.Wrapper() { 128 @Override 129 public View createWrapper(View root) { 130 Context context = root.getContext(); 131 ShadowOverlayContainer wrapper = createShadowOverlayContainer(context); 132 wrapper.setLayoutParams( 133 new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 134 return wrapper; 135 } 136 @Override 137 public void wrap(View wrapper, View wrapped) { 138 ((ShadowOverlayContainer) wrapper).wrap(wrapped); 139 } 140 }; 141 } 142 } 143 144 /** 145 * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container 146 * before using shadow. Depending on Shadow type, optical bounds might be applied. 147 */ 148 public void prepareParentForShadow(ViewGroup parent) { 149 if (mShadowType == SHADOW_STATIC) { 150 StaticShadowHelper.getInstance().prepareParent(parent); 151 } 152 } 153 154 void useDynamicShadow(Context context) { 155 Resources res = context.getResources(); 156 useDynamicShadow(res.getDimension(R.dimen.lb_material_shadow_normal_z), 157 res.getDimension(R.dimen.lb_material_shadow_focused_z)); 158 } 159 160 void useDynamicShadow(float unfocusedZ, float focusedZ) { 161 mShadowType = SHADOW_DYNAMIC; 162 mUnfocusedZ = unfocusedZ; 163 mFocusedZ = focusedZ; 164 } 165 166 public int getShadowType() { 167 return mShadowType; 168 } 169 170 public boolean needsOverlay() { 171 return mNeedsOverlay; 172 } 173 174 public boolean needsRoundedCorner() { 175 return mNeedsRoundedCorner; 176 } 177 178 ShadowOverlayContainer createShadowOverlayContainer(Context context) { 179 return new ShadowOverlayContainer(context, mShadowType, mNeedsOverlay, 180 mNeedsRoundedCorner); 181 } 182 183 public ItemBridgeAdapter.Wrapper getWrapper() { 184 return mCardWrapper; 185 } 186 187 public void setOverlayColor(View view, int color) { 188 if (mNeedsWrapper) { 189 ((ShadowOverlayContainer) view).setOverlayColor(color); 190 } else { 191 Drawable d = ForegroundHelper.getInstance().getForeground(view); 192 if (d instanceof ColorDrawable) { 193 ((ColorDrawable) d).setColor(color); 194 } else { 195 ForegroundHelper.getInstance().setForeground(view, new ColorDrawable(color)); 196 } 197 } 198 } 199 200 /** 201 * Called on view is created. 202 * @param view 203 */ 204 public void onViewCreated(View view) { 205 if (!mNeedsWrapper) { 206 if (!mNeedsShadow) { 207 if (mNeedsRoundedCorner) { 208 RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true); 209 } 210 } else { 211 if (mShadowType == SHADOW_DYNAMIC) { 212 Object tag = ShadowHelper.getInstance().addDynamicShadow( 213 view, mUnfocusedZ, mFocusedZ, mNeedsRoundedCorner); 214 view.setTag(R.id.lb_shadow_impl, tag); 215 } 216 } 217 } 218 } 219 220 public static Object getNoneWrapperDyamicShadowImpl(View view) { 221 return view.getTag(R.id.lb_shadow_impl); 222 } 223 224 /** 225 * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused. 226 */ 227 public static void setShadowFocusLevel(View view, float level) { 228 if (view instanceof ShadowOverlayContainer) { 229 ((ShadowOverlayContainer) view).setShadowFocusLevel(level); 230 } else { 231 setShadowFocusLevel(getNoneWrapperDyamicShadowImpl(view), SHADOW_DYNAMIC, level); 232 } 233 } 234 235 static void setShadowFocusLevel(Object impl, int shadowType, float level) { 236 if (impl != null) { 237 if (level < 0f) { 238 level = 0f; 239 } else if (level > 1f) { 240 level = 1f; 241 } 242 switch (shadowType) { 243 case SHADOW_DYNAMIC: 244 ShadowHelper.getInstance().setShadowFocusLevel(impl, level); 245 break; 246 case SHADOW_STATIC: 247 StaticShadowHelper.getInstance().setShadowFocusLevel(impl, level); 248 break; 249 } 250 } 251 } 252} 253