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