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