ShadowOverlayContainer.java revision 4c94efdb022031d2cbf5e80c8aa3703b01d78f68
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)} must be first called on the container to initialize
37 * shadows and/or color overlay.  Then call {@link #wrap(View)} to insert wrapped view
38 * 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 and/or color overlay.  Both are optional.
85     */
86    public void initialize(boolean hasShadow, boolean hasColorDimOverlay) {
87        if (mInitialized) {
88            throw new IllegalStateException();
89        }
90        mInitialized = true;
91        if (hasShadow) {
92            mShadowImpl = ShadowHelper.getInstance().addShadow(this);
93        }
94        if (hasColorDimOverlay) {
95            mColorDimOverlay = LayoutInflater.from(getContext())
96                    .inflate(R.layout.lb_card_color_overlay, this, false);
97            addView(mColorDimOverlay);
98        }
99    }
100
101    /**
102     * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
103     */
104    public void setShadowFocusLevel(float level) {
105        if (mShadowImpl != null) {
106            if (level < 0f) {
107                level = 0f;
108            } else if (level > 1f) {
109                level = 1f;
110            }
111            ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
112        }
113    }
114
115    /**
116     * Set color (with alpha) of the overlay.
117     */
118    public void setOverlayColor(int overlayColor) {
119        if (mColorDimOverlay != null) {
120            mColorDimOverlay.setBackgroundColor(overlayColor);
121        }
122    }
123
124    /**
125     * Inserts view into the wrapper.
126     */
127    public void wrap(View view) {
128        if (!mInitialized || mWrappedView != null) {
129            throw new IllegalStateException();
130        }
131        if (mColorDimOverlay != null) {
132            addView(view, indexOfChild(mColorDimOverlay));
133        } else {
134            addView(view);
135        }
136        mWrappedView = view;
137    }
138
139    @Override
140    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
141        if (mWrappedView == null) {
142            throw new IllegalStateException();
143        }
144        // padding and child margin are not supported.
145        // first measure the wrapped view, then measure the shadow view and/or overlay view.
146        int childWidthMeasureSpec, childHeightMeasureSpec;
147        LayoutParams lp = mWrappedView.getLayoutParams();
148        if (lp.width == LayoutParams.MATCH_PARENT) {
149            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
150                    (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
151        } else {
152            childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
153        }
154        if (lp.height == LayoutParams.MATCH_PARENT) {
155            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
156                    (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
157        } else {
158            childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
159        }
160        mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
161
162        int measuredWidth = mWrappedView.getMeasuredWidth();
163        int measuredHeight = mWrappedView.getMeasuredHeight();
164
165        for (int i = 0; i < getChildCount(); i++) {
166            View child = getChildAt(i);
167            if (child == mWrappedView) {
168                continue;
169            }
170            lp = child.getLayoutParams();
171            if (lp.width == LayoutParams.MATCH_PARENT) {
172                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
173                        (measuredWidth, MeasureSpec.EXACTLY);
174            } else {
175                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
176            }
177
178            if (lp.height == LayoutParams.MATCH_PARENT) {
179                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
180                        (measuredHeight, MeasureSpec.EXACTLY);
181            } else {
182                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
183            }
184            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
185        }
186        setMeasuredDimension(measuredWidth, measuredHeight);
187    }
188
189    @Override
190    protected void onLayout(boolean changed, int l, int t, int r, int b) {
191        final int count = getChildCount();
192        for (int i = 0; i < count; i++) {
193            final View child = getChildAt(i);
194            if (child.getVisibility() != GONE) {
195                final int width = child.getMeasuredWidth();
196                final int height = child.getMeasuredHeight();
197                child.layout(0, 0, width, height);
198            }
199        }
200        if (mWrappedView != null) {
201            sTempRect.left = (int) mWrappedView.getPivotX();
202            sTempRect.top = (int) mWrappedView.getPivotY();
203            offsetDescendantRectToMyCoords(mWrappedView, sTempRect);
204            setPivotX(sTempRect.left);
205            setPivotY(sTempRect.top);
206        }
207    }
208
209}
210