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