ShadowOverlayContainer.java revision e8ae51595e52a0acc36bfd9261f4f2634bccdde0
1/* 2 * Copyright (C) 2014 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.support.annotation.ColorInt; 18import android.support.v17.leanback.R; 19import android.util.AttributeSet; 20import android.view.LayoutInflater; 21import android.view.View; 22import android.view.ViewGroup; 23import android.graphics.Rect; 24 25/** 26 * Provides an SDK version-independent wrapper to support shadows, color overlays, and rounded 27 * corners. 28 * <p> 29 * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container 30 * before using shadow. Depending on sdk version, optical bounds might be applied 31 * to parent. 32 * </p> 33 * <p> 34 * If shadows can appear outside the bounds of the parent view, setClipChildren(false) must 35 * be called on the grandparent view. 36 * </p> 37 * <p> 38 * {@link #initialize(boolean, boolean, boolean)} must be first called on the container. 39 * Then call {@link #wrap(View)} to insert the wrapped view into the container. 40 * </p> 41 * <p> 42 * Call {@link #setShadowFocusLevel(float)} to control the strength of the shadow (focused shadows 43 * cast stronger shadows). 44 * </p> 45 * <p> 46 * Call {@link #setOverlayColor(int)} to control overlay color. 47 * </p> 48 */ 49public class ShadowOverlayContainer extends ViewGroup { 50 51 /** 52 * No shadow. 53 */ 54 public static final int SHADOW_NONE = 1; 55 56 /** 57 * Shadows are fixed. 58 */ 59 public static final int SHADOW_STATIC = 2; 60 61 /** 62 * Shadows depend on the size, shape, and position of the view. 63 */ 64 public static final int SHADOW_DYNAMIC = 3; 65 66 private boolean mInitialized; 67 private View mColorDimOverlay; 68 private Object mShadowImpl; 69 private View mWrappedView; 70 private boolean mRoundedCorners; 71 private int mShadowType = SHADOW_NONE; 72 private float mUnfocusedZ; 73 private float mFocusedZ; 74 private static final Rect sTempRect = new Rect(); 75 76 public ShadowOverlayContainer(Context context) { 77 this(context, null, 0); 78 } 79 80 public ShadowOverlayContainer(Context context, AttributeSet attrs) { 81 this(context, attrs, 0); 82 } 83 84 public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) { 85 super(context, attrs, defStyle); 86 useStaticShadow(); 87 useDynamicShadow(); 88 } 89 90 /** 91 * Return true if the platform sdk supports shadow. 92 */ 93 public static boolean supportsShadow() { 94 return StaticShadowHelper.getInstance().supportsShadow(); 95 } 96 97 /** 98 * Returns true if the platform sdk supports dynamic shadows. 99 */ 100 public static boolean supportsDynamicShadow() { 101 return ShadowHelper.getInstance().supportsDynamicShadow(); 102 } 103 104 /** 105 * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container 106 * before using shadow. Depending on sdk version, optical bounds might be applied 107 * to parent. 108 */ 109 public static void prepareParentForShadow(ViewGroup parent) { 110 StaticShadowHelper.getInstance().prepareParent(parent); 111 } 112 113 /** 114 * Sets the shadow type to {@link #SHADOW_DYNAMIC} if supported. 115 */ 116 public void useDynamicShadow() { 117 useDynamicShadow(getResources().getDimension(R.dimen.lb_material_shadow_normal_z), 118 getResources().getDimension(R.dimen.lb_material_shadow_focused_z)); 119 } 120 121 /** 122 * Sets the shadow type to {@link #SHADOW_DYNAMIC} if supported and sets the elevation/Z 123 * values to the given parameteres. 124 */ 125 public void useDynamicShadow(float unfocusedZ, float focusedZ) { 126 if (mInitialized) { 127 throw new IllegalStateException("Already initialized"); 128 } 129 if (supportsDynamicShadow()) { 130 mShadowType = SHADOW_DYNAMIC; 131 mUnfocusedZ = unfocusedZ; 132 mFocusedZ = focusedZ; 133 } 134 } 135 136 /** 137 * Sets the shadow type to {@link #SHADOW_STATIC} if supported. 138 */ 139 public void useStaticShadow() { 140 if (mInitialized) { 141 throw new IllegalStateException("Already initialized"); 142 } 143 if (supportsShadow()) { 144 mShadowType = SHADOW_STATIC; 145 } 146 } 147 148 /** 149 * Returns the shadow type, one of {@link #SHADOW_NONE}, {@link #SHADOW_STATIC}, or 150 * {@link #SHADOW_DYNAMIC}. 151 */ 152 public int getShadowType() { 153 return mShadowType; 154 } 155 156 /** 157 * Initialize shadows, color overlay. 158 * @deprecated use {@link #initialize(boolean, boolean, boolean)} instead. 159 */ 160 @Deprecated 161 public void initialize(boolean hasShadow, boolean hasColorDimOverlay) { 162 initialize(hasShadow, hasColorDimOverlay, true); 163 } 164 165 /** 166 * Initialize shadows, color overlay, and rounded corners. All are optional. 167 */ 168 public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) { 169 if (mInitialized) { 170 throw new IllegalStateException(); 171 } 172 mInitialized = true; 173 if (hasShadow) { 174 switch (mShadowType) { 175 case SHADOW_DYNAMIC: 176 mShadowImpl = ShadowHelper.getInstance().addDynamicShadow( 177 this, mUnfocusedZ, mFocusedZ, roundedCorners); 178 break; 179 case SHADOW_STATIC: 180 mShadowImpl = StaticShadowHelper.getInstance().addStaticShadow( 181 this, roundedCorners); 182 break; 183 } 184 } 185 mRoundedCorners = roundedCorners; 186 if (hasColorDimOverlay) { 187 mColorDimOverlay = LayoutInflater.from(getContext()) 188 .inflate(R.layout.lb_card_color_overlay, this, false); 189 addView(mColorDimOverlay); 190 } 191 } 192 193 /** 194 * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused. 195 */ 196 public void setShadowFocusLevel(float level) { 197 if (mShadowImpl != null) { 198 if (level < 0f) { 199 level = 0f; 200 } else if (level > 1f) { 201 level = 1f; 202 } 203 switch (mShadowType) { 204 case SHADOW_DYNAMIC: 205 ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level); 206 break; 207 case SHADOW_STATIC: 208 StaticShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level); 209 break; 210 } 211 } 212 } 213 214 /** 215 * Set color (with alpha) of the overlay. 216 */ 217 public void setOverlayColor(@ColorInt int overlayColor) { 218 if (mColorDimOverlay != null) { 219 mColorDimOverlay.setBackgroundColor(overlayColor); 220 } 221 } 222 223 /** 224 * Inserts view into the wrapper. 225 */ 226 public void wrap(View view) { 227 if (!mInitialized || mWrappedView != null) { 228 throw new IllegalStateException(); 229 } 230 if (mColorDimOverlay != null) { 231 addView(view, indexOfChild(mColorDimOverlay)); 232 } else { 233 addView(view); 234 } 235 mWrappedView = view; 236 if (mRoundedCorners) { 237 RoundedRectHelper.getInstance().setClipToRoundedOutline(mWrappedView, true); 238 } 239 } 240 241 /** 242 * Returns the wrapper view. 243 */ 244 public View getWrappedView() { 245 return mWrappedView; 246 } 247 248 @Override 249 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 250 if (mWrappedView == null) { 251 throw new IllegalStateException(); 252 } 253 // padding and child margin are not supported. 254 // first measure the wrapped view, then measure the shadow view and/or overlay view. 255 int childWidthMeasureSpec, childHeightMeasureSpec; 256 LayoutParams lp = mWrappedView.getLayoutParams(); 257 if (lp.width == LayoutParams.MATCH_PARENT) { 258 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec 259 (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY); 260 } else { 261 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 262 } 263 if (lp.height == LayoutParams.MATCH_PARENT) { 264 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec 265 (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY); 266 } else { 267 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height); 268 } 269 mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 270 271 int measuredWidth = mWrappedView.getMeasuredWidth(); 272 int measuredHeight = mWrappedView.getMeasuredHeight(); 273 274 for (int i = 0; i < getChildCount(); i++) { 275 View child = getChildAt(i); 276 if (child == mWrappedView) { 277 continue; 278 } 279 lp = child.getLayoutParams(); 280 if (lp.width == LayoutParams.MATCH_PARENT) { 281 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec 282 (measuredWidth, MeasureSpec.EXACTLY); 283 } else { 284 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 285 } 286 287 if (lp.height == LayoutParams.MATCH_PARENT) { 288 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec 289 (measuredHeight, MeasureSpec.EXACTLY); 290 } else { 291 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height); 292 } 293 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 294 } 295 setMeasuredDimension(measuredWidth, measuredHeight); 296 } 297 298 @Override 299 protected void onLayout(boolean changed, int l, int t, int r, int b) { 300 final int count = getChildCount(); 301 for (int i = 0; i < count; i++) { 302 final View child = getChildAt(i); 303 if (child.getVisibility() != GONE) { 304 final int width = child.getMeasuredWidth(); 305 final int height = child.getMeasuredHeight(); 306 child.layout(0, 0, width, height); 307 } 308 } 309 if (mWrappedView != null) { 310 sTempRect.left = (int) mWrappedView.getPivotX(); 311 sTempRect.top = (int) mWrappedView.getPivotY(); 312 offsetDescendantRectToMyCoords(mWrappedView, sTempRect); 313 setPivotX(sTempRect.left); 314 setPivotY(sTempRect.top); 315 } 316 } 317 318} 319