ShadowOverlayContainer.java revision ca48c80e6b6d90f6362144950c5f20a8f6839e9d
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 * ShadowOverlayContainer Provides a SDK version independent wrapper container 26 * to take care of shadow and/or color overlay. 27 * <p> 28 * Shadow and color dimmer overlay are both optional. When shadow is used, it's 29 * user's responsibility to properly call setClipChildren(false) on parent views if 30 * the shadow can appear outside bounds of parent views. 31 * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container 32 * before using shadow. Depending on sdk version, optical bounds might be applied 33 * to parent. 34 * </p> 35 * <p> 36 * {@link #initialize(boolean, boolean, boolean)} must be first called on the container 37 * to initialize shadows and/or color overlay. Then call {@link #wrap(View)} to insert 38 * wrapped view into container. 39 * </p> 40 * <p> 41 * Call {@link #setShadowFocusLevel(float)} to control shadow alpha. 42 * </p> 43 * <p> 44 * Call {@link #setOverlayColor(int)} to control overlay color. 45 * </p> 46 */ 47public class ShadowOverlayContainer extends ViewGroup { 48 49 private boolean mInitialized; 50 private View mColorDimOverlay; 51 private Object mShadowImpl; 52 private View mWrappedView; 53 private static final Rect sTempRect = new Rect(); 54 55 public ShadowOverlayContainer(Context context) { 56 this(context, null, 0); 57 } 58 59 public ShadowOverlayContainer(Context context, AttributeSet attrs) { 60 this(context, attrs, 0); 61 } 62 63 public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) { 64 super(context, attrs, defStyle); 65 } 66 67 /** 68 * Return true if the platform sdk supports shadow. 69 */ 70 public static boolean supportsShadow() { 71 return ShadowHelper.getInstance().supportsShadow(); 72 } 73 74 /** 75 * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container 76 * before using shadow. Depending on sdk version, optical bounds might be applied 77 * to parent. 78 */ 79 public static void prepareParentForShadow(ViewGroup parent) { 80 ShadowHelper.getInstance().prepareParent(parent); 81 } 82 83 /** 84 * Initialize shadows, color overlay, and rounded corners. All are optional. 85 */ 86 public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) { 87 if (mInitialized) { 88 throw new IllegalStateException(); 89 } 90 mInitialized = true; 91 if (hasShadow) { 92 mShadowImpl = ShadowHelper.getInstance().addShadow(this, roundedCorners); 93 } else if (roundedCorners) { 94 RoundedRectHelper.getInstance().setRoundedRectBackground(this, 95 android.graphics.Color.TRANSPARENT); 96 } 97 if (hasColorDimOverlay) { 98 mColorDimOverlay = LayoutInflater.from(getContext()) 99 .inflate(R.layout.lb_card_color_overlay, this, false); 100 addView(mColorDimOverlay); 101 } 102 } 103 104 /** 105 * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused. 106 */ 107 public void setShadowFocusLevel(float level) { 108 if (mShadowImpl != null) { 109 if (level < 0f) { 110 level = 0f; 111 } else if (level > 1f) { 112 level = 1f; 113 } 114 ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level); 115 } 116 } 117 118 /** 119 * Set color (with alpha) of the overlay. 120 */ 121 public void setOverlayColor(int overlayColor) { 122 if (mColorDimOverlay != null) { 123 mColorDimOverlay.setBackgroundColor(overlayColor); 124 } 125 } 126 127 /** 128 * Inserts view into the wrapper. 129 */ 130 public void wrap(View view) { 131 if (!mInitialized || mWrappedView != null) { 132 throw new IllegalStateException(); 133 } 134 if (mColorDimOverlay != null) { 135 addView(view, indexOfChild(mColorDimOverlay)); 136 } else { 137 addView(view); 138 } 139 mWrappedView = view; 140 } 141 142 @Override 143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 144 if (mWrappedView == null) { 145 throw new IllegalStateException(); 146 } 147 // padding and child margin are not supported. 148 // first measure the wrapped view, then measure the shadow view and/or overlay view. 149 int childWidthMeasureSpec, childHeightMeasureSpec; 150 LayoutParams lp = mWrappedView.getLayoutParams(); 151 if (lp.width == LayoutParams.MATCH_PARENT) { 152 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec 153 (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY); 154 } else { 155 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 156 } 157 if (lp.height == LayoutParams.MATCH_PARENT) { 158 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec 159 (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY); 160 } else { 161 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height); 162 } 163 mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 164 165 int measuredWidth = mWrappedView.getMeasuredWidth(); 166 int measuredHeight = mWrappedView.getMeasuredHeight(); 167 168 for (int i = 0; i < getChildCount(); i++) { 169 View child = getChildAt(i); 170 if (child == mWrappedView) { 171 continue; 172 } 173 lp = child.getLayoutParams(); 174 if (lp.width == LayoutParams.MATCH_PARENT) { 175 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec 176 (measuredWidth, MeasureSpec.EXACTLY); 177 } else { 178 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 179 } 180 181 if (lp.height == LayoutParams.MATCH_PARENT) { 182 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec 183 (measuredHeight, MeasureSpec.EXACTLY); 184 } else { 185 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height); 186 } 187 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 188 } 189 setMeasuredDimension(measuredWidth, measuredHeight); 190 } 191 192 @Override 193 protected void onLayout(boolean changed, int l, int t, int r, int b) { 194 final int count = getChildCount(); 195 for (int i = 0; i < count; i++) { 196 final View child = getChildAt(i); 197 if (child.getVisibility() != GONE) { 198 final int width = child.getMeasuredWidth(); 199 final int height = child.getMeasuredHeight(); 200 child.layout(0, 0, width, height); 201 } 202 } 203 if (mWrappedView != null) { 204 sTempRect.left = (int) mWrappedView.getPivotX(); 205 sTempRect.top = (int) mWrappedView.getPivotY(); 206 offsetDescendantRectToMyCoords(mWrappedView, sTempRect); 207 setPivotX(sTempRect.left); 208 setPivotY(sTempRect.top); 209 } 210 } 211 212} 213