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