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