ShadowOverlayContainer.java revision a76db0cf97b5f05ef0a5e1f6d999933f738f4a3e
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 private boolean mInitialized; 52 private View mColorDimOverlay; 53 private Object mShadowImpl; 54 private View mWrappedView; 55 private boolean mRoundedCorners; 56 private static final Rect sTempRect = new Rect(); 57 58 public ShadowOverlayContainer(Context context) { 59 this(context, null, 0); 60 } 61 62 public ShadowOverlayContainer(Context context, AttributeSet attrs) { 63 this(context, attrs, 0); 64 } 65 66 public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) { 67 super(context, attrs, defStyle); 68 } 69 70 /** 71 * Return true if the platform sdk supports shadow. 72 */ 73 public static boolean supportsShadow() { 74 return ShadowHelper.getInstance().supportsShadow(); 75 } 76 77 /** 78 * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container 79 * before using shadow. Depending on sdk version, optical bounds might be applied 80 * to parent. 81 */ 82 public static void prepareParentForShadow(ViewGroup parent) { 83 ShadowHelper.getInstance().prepareParent(parent); 84 } 85 86 /** 87 * Initialize shadows, color overlay. 88 * @deprecated use {@link #initialize(boolean, boolean, boolean)} instead. 89 */ 90 @Deprecated 91 public void initialize(boolean hasShadow, boolean hasColorDimOverlay) { 92 initialize(hasShadow, hasColorDimOverlay, true); 93 } 94 95 /** 96 * Initialize shadows, color overlay, and rounded corners. All are optional. 97 */ 98 public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) { 99 if (mInitialized) { 100 throw new IllegalStateException(); 101 } 102 mInitialized = true; 103 if (hasShadow) { 104 mShadowImpl = ShadowHelper.getInstance().addShadow(this, roundedCorners); 105 } 106 mRoundedCorners = roundedCorners; 107 if (hasColorDimOverlay) { 108 mColorDimOverlay = LayoutInflater.from(getContext()) 109 .inflate(R.layout.lb_card_color_overlay, this, false); 110 addView(mColorDimOverlay); 111 if (roundedCorners) { 112 RoundedRectHelper.getInstance().setClipToRoundedOutline(mColorDimOverlay, true); 113 } 114 } 115 } 116 117 /** 118 * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused. 119 */ 120 public void setShadowFocusLevel(float level) { 121 if (mShadowImpl != null) { 122 if (level < 0f) { 123 level = 0f; 124 } else if (level > 1f) { 125 level = 1f; 126 } 127 ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level); 128 } 129 } 130 131 /** 132 * Set color (with alpha) of the overlay. 133 */ 134 public void setOverlayColor(@ColorInt int overlayColor) { 135 if (mColorDimOverlay != null) { 136 mColorDimOverlay.setBackgroundColor(overlayColor); 137 } 138 } 139 140 /** 141 * Inserts view into the wrapper. 142 */ 143 public void wrap(View view) { 144 if (!mInitialized || mWrappedView != null) { 145 throw new IllegalStateException(); 146 } 147 if (mColorDimOverlay != null) { 148 addView(view, indexOfChild(mColorDimOverlay)); 149 } else { 150 addView(view); 151 } 152 mWrappedView = view; 153 if (mRoundedCorners) { 154 RoundedRectHelper.getInstance().setClipToRoundedOutline(mWrappedView, true); 155 } 156 } 157 158 @Override 159 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 160 if (mWrappedView == null) { 161 throw new IllegalStateException(); 162 } 163 // padding and child margin are not supported. 164 // first measure the wrapped view, then measure the shadow view and/or overlay view. 165 int childWidthMeasureSpec, childHeightMeasureSpec; 166 LayoutParams lp = mWrappedView.getLayoutParams(); 167 if (lp.width == LayoutParams.MATCH_PARENT) { 168 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec 169 (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY); 170 } else { 171 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 172 } 173 if (lp.height == LayoutParams.MATCH_PARENT) { 174 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec 175 (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY); 176 } else { 177 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height); 178 } 179 mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 180 181 int measuredWidth = mWrappedView.getMeasuredWidth(); 182 int measuredHeight = mWrappedView.getMeasuredHeight(); 183 184 for (int i = 0; i < getChildCount(); i++) { 185 View child = getChildAt(i); 186 if (child == mWrappedView) { 187 continue; 188 } 189 lp = child.getLayoutParams(); 190 if (lp.width == LayoutParams.MATCH_PARENT) { 191 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec 192 (measuredWidth, MeasureSpec.EXACTLY); 193 } else { 194 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 195 } 196 197 if (lp.height == LayoutParams.MATCH_PARENT) { 198 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec 199 (measuredHeight, MeasureSpec.EXACTLY); 200 } else { 201 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height); 202 } 203 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 204 } 205 setMeasuredDimension(measuredWidth, measuredHeight); 206 } 207 208 @Override 209 protected void onLayout(boolean changed, int l, int t, int r, int b) { 210 final int count = getChildCount(); 211 for (int i = 0; i < count; i++) { 212 final View child = getChildAt(i); 213 if (child.getVisibility() != GONE) { 214 final int width = child.getMeasuredWidth(); 215 final int height = child.getMeasuredHeight(); 216 child.layout(0, 0, width, height); 217 } 218 } 219 if (mWrappedView != null) { 220 sTempRect.left = (int) mWrappedView.getPivotX(); 221 sTempRect.top = (int) mWrappedView.getPivotY(); 222 offsetDescendantRectToMyCoords(mWrappedView, sTempRect); 223 setPivotX(sTempRect.left); 224 setPivotY(sTempRect.top); 225 } 226 } 227 228} 229