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