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