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.support.v17.leanback.R;
17import android.support.v17.leanback.graphics.ColorOverlayDimmer;
18import android.view.View;
19import android.view.animation.AccelerateDecelerateInterpolator;
20import android.view.animation.Interpolator;
21import android.animation.TimeAnimator;
22import android.content.res.Resources;
23import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_NONE;
24import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_SMALL;
25import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_XSMALL;
26import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_MEDIUM;
27import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_LARGE;
28
29/**
30 * Sets up the highlighting behavior when an item gains focus.
31 */
32public class FocusHighlightHelper {
33
34    static boolean isValidZoomIndex(int zoomIndex) {
35        return zoomIndex == ZOOM_FACTOR_NONE || getResId(zoomIndex) > 0;
36    }
37
38    private static int getResId(int zoomIndex) {
39        switch (zoomIndex) {
40            case ZOOM_FACTOR_SMALL:
41                return R.fraction.lb_focus_zoom_factor_small;
42            case ZOOM_FACTOR_XSMALL:
43                return R.fraction.lb_focus_zoom_factor_xsmall;
44            case ZOOM_FACTOR_MEDIUM:
45                return R.fraction.lb_focus_zoom_factor_medium;
46            case ZOOM_FACTOR_LARGE:
47                return R.fraction.lb_focus_zoom_factor_large;
48            default:
49                return 0;
50        }
51    }
52
53
54    static class FocusAnimator implements TimeAnimator.TimeListener {
55        private final View mView;
56        private final int mDuration;
57        private final ShadowOverlayContainer mWrapper;
58        private final float mScaleDiff;
59        private float mFocusLevel = 0f;
60        private float mFocusLevelStart;
61        private float mFocusLevelDelta;
62        private final TimeAnimator mAnimator = new TimeAnimator();
63        private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
64        private final ColorOverlayDimmer mDimmer;
65
66        void animateFocus(boolean select, boolean immediate) {
67            endAnimation();
68            final float end = select ? 1 : 0;
69            if (immediate) {
70                setFocusLevel(end);
71            } else if (mFocusLevel != end) {
72                mFocusLevelStart = mFocusLevel;
73                mFocusLevelDelta = end - mFocusLevelStart;
74                mAnimator.start();
75            }
76        }
77
78        FocusAnimator(View view, float scale, boolean useDimmer, int duration) {
79            mView = view;
80            mDuration = duration;
81            mScaleDiff = scale - 1f;
82            if (view instanceof ShadowOverlayContainer) {
83                mWrapper = (ShadowOverlayContainer) view;
84            } else {
85                mWrapper = null;
86            }
87            mAnimator.setTimeListener(this);
88            if (useDimmer) {
89                mDimmer = ColorOverlayDimmer.createDefault(view.getContext());
90            } else {
91                mDimmer = null;
92            }
93        }
94
95        void setFocusLevel(float level) {
96            mFocusLevel = level;
97            float scale = 1f + mScaleDiff * level;
98            mView.setScaleX(scale);
99            mView.setScaleY(scale);
100            if (mWrapper != null) {
101                mWrapper.setShadowFocusLevel(level);
102            } else {
103                ShadowOverlayHelper.setNoneWrapperShadowFocusLevel(mView, level);
104            }
105            if (mDimmer != null) {
106                mDimmer.setActiveLevel(level);
107                int color = mDimmer.getPaint().getColor();
108                if (mWrapper != null) {
109                    mWrapper.setOverlayColor(color);
110                } else {
111                    ShadowOverlayHelper.setNoneWrapperOverlayColor(mView, color);
112                }
113            }
114        }
115
116        float getFocusLevel() {
117            return mFocusLevel;
118        }
119
120        void endAnimation() {
121            mAnimator.end();
122        }
123
124        @Override
125        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
126            float fraction;
127            if (totalTime >= mDuration) {
128                fraction = 1;
129                mAnimator.end();
130            } else {
131                fraction = (float) (totalTime / (double) mDuration);
132            }
133            if (mInterpolator != null) {
134                fraction = mInterpolator.getInterpolation(fraction);
135            }
136            setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
137        }
138    }
139
140    static class BrowseItemFocusHighlight implements FocusHighlightHandler {
141        private static final int DURATION_MS = 150;
142
143        private int mScaleIndex;
144        private final boolean mUseDimmer;
145
146        BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer) {
147            if (!isValidZoomIndex(zoomIndex)) {
148                throw new IllegalArgumentException("Unhandled zoom index");
149            }
150            mScaleIndex = zoomIndex;
151            mUseDimmer = useDimmer;
152        }
153
154        private float getScale(Resources res) {
155            return mScaleIndex == ZOOM_FACTOR_NONE ? 1f :
156                    res.getFraction(getResId(mScaleIndex), 1, 1);
157        }
158
159        @Override
160        public void onItemFocused(View view, boolean hasFocus) {
161            view.setSelected(hasFocus);
162            getOrCreateAnimator(view).animateFocus(hasFocus, false);
163        }
164
165        @Override
166        public void onInitializeView(View view) {
167            getOrCreateAnimator(view).animateFocus(false, true);
168        }
169
170        private FocusAnimator getOrCreateAnimator(View view) {
171            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
172            if (animator == null) {
173                animator = new FocusAnimator(
174                        view, getScale(view.getResources()), mUseDimmer, DURATION_MS);
175                view.setTag(R.id.lb_focus_animator, animator);
176            }
177            return animator;
178        }
179
180    }
181
182    /**
183     * Sets up the focus highlight behavior of a focused item in browse list row.
184     * @param zoomIndex One of {@link FocusHighlight#ZOOM_FACTOR_SMALL}
185     * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}
186     * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}
187     * {@link FocusHighlight#ZOOM_FACTOR_LARGE}
188     * {@link FocusHighlight#ZOOM_FACTOR_NONE}.
189     * @param useDimmer Allow dimming browse item when unselected.
190     * @param adapter  adapter of the list row.
191     */
192    public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex,
193            boolean useDimmer) {
194        adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex, useDimmer));
195    }
196
197    /**
198     * Sets up the focus highlight behavior of a focused item in header list.
199     * @param gridView  the header list.
200     */
201    public static void setupHeaderItemFocusHighlight(VerticalGridView gridView) {
202        if (gridView.getAdapter() instanceof ItemBridgeAdapter) {
203            ((ItemBridgeAdapter) gridView.getAdapter())
204                    .setFocusHighlight(new HeaderItemFocusHighlight(gridView));
205        }
206    }
207
208    static class HeaderItemFocusHighlight implements FocusHighlightHandler {
209        private static boolean sInitialized;
210        private static float sSelectScale;
211        private static int sDuration;
212        private BaseGridView mGridView;
213
214        HeaderItemFocusHighlight(BaseGridView gridView) {
215            mGridView = gridView;
216            lazyInit(gridView.getContext().getResources());
217        }
218
219        private static void lazyInit(Resources res) {
220            if (!sInitialized) {
221                sSelectScale =
222                        Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale));
223                sDuration =
224                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));
225                sInitialized = true;
226            }
227        }
228
229        class HeaderFocusAnimator extends FocusAnimator {
230
231            ItemBridgeAdapter.ViewHolder mViewHolder;
232            HeaderFocusAnimator(View view, float scale, int duration) {
233                super(view, scale, false, duration);
234                mViewHolder = (ItemBridgeAdapter.ViewHolder) mGridView.getChildViewHolder(view);
235            }
236
237            @Override
238            void setFocusLevel(float level) {
239                Presenter presenter = mViewHolder.getPresenter();
240                if (presenter instanceof RowHeaderPresenter) {
241                    ((RowHeaderPresenter) presenter).setSelectLevel(
242                            ((RowHeaderPresenter.ViewHolder) mViewHolder.getViewHolder()), level);
243                }
244                super.setFocusLevel(level);
245            }
246
247        }
248
249        private void viewFocused(View view, boolean hasFocus) {
250            view.setSelected(hasFocus);
251            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
252            if (animator == null) {
253                animator = new HeaderFocusAnimator(view, sSelectScale, sDuration);
254                view.setTag(R.id.lb_focus_animator, animator);
255            }
256            animator.animateFocus(hasFocus, false);
257        }
258
259        @Override
260        public void onItemFocused(View view, boolean hasFocus) {
261            viewFocused(view, hasFocus);
262        }
263
264        @Override
265        public void onInitializeView(View view) {
266        }
267
268    }
269}
270