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