ShadowOverlayHelper.java revision 20921beeb51f39558a7ac3b646cce8e342cb7183
1/*
2 * Copyright (C) 2015 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.content.res.Resources;
18import android.graphics.drawable.ColorDrawable;
19import android.graphics.drawable.Drawable;
20import android.os.Build;
21import android.support.v17.leanback.R;
22import android.support.v17.leanback.system.Settings;
23import android.util.AttributeSet;
24import android.view.ViewGroup;
25import android.view.ViewGroup.LayoutParams;
26import android.view.View;
27
28
29/**
30 * Helper for shadow.
31 * @hide
32 */
33public final class ShadowOverlayHelper {
34
35    /**
36     * No shadow.
37     */
38    public static final int SHADOW_NONE = 1;
39
40    /**
41     * Shadows are fixed.
42     */
43    public static final int SHADOW_STATIC = 2;
44
45    /**
46     * Shadows depend on the size, shape, and position of the view.
47     */
48    public static final int SHADOW_DYNAMIC = 3;
49
50    int mShadowType = SHADOW_NONE;
51    boolean mNeedsOverlay;
52    boolean mNeedsRoundedCorner;
53    boolean mNeedsShadow;
54    boolean mNeedsWrapper;
55    private ItemBridgeAdapter.Wrapper mCardWrapper;
56
57    float mUnfocusedZ;
58    float mFocusedZ;
59
60    /**
61     * Return true if the platform sdk supports shadow.
62     */
63    public static boolean supportsShadow() {
64        return StaticShadowHelper.getInstance().supportsShadow();
65    }
66
67    /**
68     * Returns true if the platform sdk supports dynamic shadows.
69     */
70    public static boolean supportsDynamicShadow() {
71        return ShadowHelper.getInstance().supportsDynamicShadow();
72    }
73
74    /**
75     * Returns true if the platform sdk supports rounded corner through outline.
76     */
77    public static boolean supportsRoundedCorner() {
78        return RoundedRectHelper.supportsRoundedCorner();
79    }
80
81    /**
82     * Returns true if view.setForeground() is supported.
83     */
84    public static boolean supportsForeground() {
85        return ForegroundHelper.supportsForeground();
86    }
87
88    /**
89     * Create ShadowHelper that includes all options.
90     *
91     * @param context               Context that required to query options
92     * @param needsOverlay          true if overlay (dim) is needed
93     * @param needsShadow           true if shadow is needed
94     * @param needsRoundedCorner    true if roundedCorner is needed.
95     * @param preferZOrder          true if prefer dynamic shadow otherwise static shadow is used.
96     */
97    public ShadowOverlayHelper(Context context,
98            boolean needsOverlay, boolean needsShadow, boolean needsRoundedCorner,
99            boolean preferZOrder) {
100        mNeedsOverlay = needsOverlay;
101        mNeedsRoundedCorner = needsRoundedCorner && supportsRoundedCorner();
102        mNeedsShadow = needsShadow && supportsShadow();
103
104        // Force to use wrapper to avoid rebuildOutline() on animating foreground drawable.
105        // See b/22724385
106        final boolean forceWrapperForOverlay = true;
107
108        // figure out shadow type and if we need use wrapper:
109        if (mNeedsShadow) {
110            // if static shadow is prefered or dynamic shadow is not supported,
111            // use static shadow,  otherwise use dynamic shadow.
112            if (!preferZOrder || !supportsDynamicShadow()) {
113                mShadowType = SHADOW_STATIC;
114                // static shadow requires ShadowOverlayContainer to support crossfading
115                // of two shadow views.
116                mNeedsWrapper = true;
117            } else {
118                useDynamicShadow(context);
119                mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay);
120            }
121        } else {
122            mShadowType = SHADOW_NONE;
123            mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay);
124        }
125
126        if (mNeedsWrapper) {
127            mCardWrapper = new ItemBridgeAdapter.Wrapper() {
128                @Override
129                public View createWrapper(View root) {
130                    Context context = root.getContext();
131                    ShadowOverlayContainer wrapper = createShadowOverlayContainer(context);
132                    wrapper.setLayoutParams(
133                            new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
134                    return wrapper;
135                }
136                @Override
137                public void wrap(View wrapper, View wrapped) {
138                    ((ShadowOverlayContainer) wrapper).wrap(wrapped);
139                }
140            };
141        }
142    }
143
144    /**
145     * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
146     * before using shadow.  Depending on Shadow type, optical bounds might be applied.
147     */
148    public void prepareParentForShadow(ViewGroup parent) {
149        if (mShadowType == SHADOW_STATIC) {
150            StaticShadowHelper.getInstance().prepareParent(parent);
151        }
152    }
153
154    void useDynamicShadow(Context context) {
155        Resources res = context.getResources();
156        useDynamicShadow(res.getDimension(R.dimen.lb_material_shadow_normal_z),
157                res.getDimension(R.dimen.lb_material_shadow_focused_z));
158    }
159
160    void useDynamicShadow(float unfocusedZ, float focusedZ) {
161        mShadowType = SHADOW_DYNAMIC;
162        mUnfocusedZ = unfocusedZ;
163        mFocusedZ = focusedZ;
164    }
165
166    public int getShadowType() {
167        return mShadowType;
168    }
169
170    public boolean needsOverlay() {
171        return mNeedsOverlay;
172    }
173
174    public boolean needsRoundedCorner() {
175        return mNeedsRoundedCorner;
176    }
177
178    ShadowOverlayContainer createShadowOverlayContainer(Context context) {
179        return new ShadowOverlayContainer(context, mShadowType, mNeedsOverlay,
180                mNeedsRoundedCorner);
181    }
182
183    public ItemBridgeAdapter.Wrapper getWrapper() {
184        return mCardWrapper;
185    }
186
187    public void setOverlayColor(View view, int color) {
188        if (mNeedsWrapper) {
189            ((ShadowOverlayContainer) view).setOverlayColor(color);
190        } else {
191            Drawable d = ForegroundHelper.getInstance().getForeground(view);
192            if (d instanceof ColorDrawable) {
193                ((ColorDrawable) d).setColor(color);
194            } else {
195                ForegroundHelper.getInstance().setForeground(view, new ColorDrawable(color));
196            }
197        }
198    }
199
200    /**
201     * Called on view is created.
202     * @param view
203     */
204    public void onViewCreated(View view) {
205        if (!mNeedsWrapper) {
206            if (!mNeedsShadow) {
207                if (mNeedsRoundedCorner) {
208                    RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
209                }
210            } else {
211                if (mShadowType == SHADOW_DYNAMIC) {
212                    Object tag = ShadowHelper.getInstance().addDynamicShadow(
213                            view, mUnfocusedZ, mFocusedZ, mNeedsRoundedCorner);
214                    view.setTag(R.id.lb_shadow_impl, tag);
215                }
216            }
217        }
218    }
219
220    public static Object getNoneWrapperDyamicShadowImpl(View view) {
221        return view.getTag(R.id.lb_shadow_impl);
222    }
223
224    /**
225     * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
226     */
227    public static void setShadowFocusLevel(View view, float level) {
228        if (view instanceof ShadowOverlayContainer) {
229            ((ShadowOverlayContainer) view).setShadowFocusLevel(level);
230        } else {
231            setShadowFocusLevel(getNoneWrapperDyamicShadowImpl(view), SHADOW_DYNAMIC, level);
232        }
233    }
234
235    static void setShadowFocusLevel(Object impl, int shadowType, float level) {
236        if (impl != null) {
237            if (level < 0f) {
238                level = 0f;
239            } else if (level > 1f) {
240                level = 1f;
241            }
242            switch (shadowType) {
243                case SHADOW_DYNAMIC:
244                    ShadowHelper.getInstance().setShadowFocusLevel(impl, level);
245                    break;
246                case SHADOW_STATIC:
247                    StaticShadowHelper.getInstance().setShadowFocusLevel(impl, level);
248                    break;
249            }
250        }
251    }
252}
253