FocusHighlightHelper.java revision a00bada00bff4a58436a39472ab14ccb7a8f619d
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 (mWrapper != null && 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                if (mDimmer != null) {
103                    mDimmer.setActiveLevel(level);
104                    mWrapper.setOverlayColor(mDimmer.getPaint().getColor());
105                }
106            }
107        }
108
109        float getFocusLevel() {
110            return mFocusLevel;
111        }
112
113        void endAnimation() {
114            mAnimator.end();
115        }
116
117        @Override
118        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
119            float fraction;
120            if (totalTime >= mDuration) {
121                fraction = 1;
122                mAnimator.end();
123            } else {
124                fraction = (float) (totalTime / (double) mDuration);
125            }
126            if (mInterpolator != null) {
127                fraction = mInterpolator.getInterpolation(fraction);
128            }
129            setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
130        }
131    }
132
133    static class BrowseItemFocusHighlight implements FocusHighlightHandler {
134        private static final int DURATION_MS = 150;
135
136        private int mScaleIndex;
137        private final boolean mUseDimmer;
138
139        BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer) {
140            if (!isValidZoomIndex(zoomIndex)) {
141                throw new IllegalArgumentException("Unhandled zoom index");
142            }
143            mScaleIndex = zoomIndex;
144            mUseDimmer = useDimmer;
145        }
146
147        private float getScale(Resources res) {
148            return mScaleIndex == ZOOM_FACTOR_NONE ? 1f :
149                    res.getFraction(getResId(mScaleIndex), 1, 1);
150        }
151
152        @Override
153        public void onItemFocused(View view, boolean hasFocus) {
154            view.setSelected(hasFocus);
155            getOrCreateAnimator(view).animateFocus(hasFocus, false);
156        }
157
158        @Override
159        public void onInitializeView(View view) {
160            getOrCreateAnimator(view).animateFocus(false, true);
161        }
162
163        private FocusAnimator getOrCreateAnimator(View view) {
164            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
165            if (animator == null) {
166                animator = new FocusAnimator(
167                        view, getScale(view.getResources()), mUseDimmer, DURATION_MS);
168                view.setTag(R.id.lb_focus_animator, animator);
169            }
170            return animator;
171        }
172
173    }
174
175    /**
176     * Sets up the focus highlight behavior of a focused item in browse list row.
177     * @param zoomIndex One of {@link FocusHighlight#ZOOM_FACTOR_SMALL}
178     * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}
179     * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}
180     * {@link FocusHighlight#ZOOM_FACTOR_LARGE}
181     * {@link FocusHighlight#ZOOM_FACTOR_NONE}.
182     * @param useDimmer Allow dimming browse item when unselected.
183     * @param adapter  adapter of the list row.
184     */
185    public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex,
186            boolean useDimmer) {
187        adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex, useDimmer));
188    }
189
190    /**
191     * Sets up the focus highlight behavior of a focused item in header list.
192     * @param gridView  the header list.
193     */
194    public static void setupHeaderItemFocusHighlight(VerticalGridView gridView) {
195        if (gridView.getAdapter() instanceof ItemBridgeAdapter) {
196            ((ItemBridgeAdapter) gridView.getAdapter())
197                    .setFocusHighlight(new HeaderItemFocusHighlight(gridView));
198        }
199    }
200
201    static class HeaderItemFocusHighlight implements FocusHighlightHandler {
202        private static boolean sInitialized;
203        private static float sSelectScale;
204        private static int sDuration;
205        private BaseGridView mGridView;
206
207        HeaderItemFocusHighlight(BaseGridView gridView) {
208            mGridView = gridView;
209            lazyInit(gridView.getContext().getResources());
210        }
211
212        private static void lazyInit(Resources res) {
213            if (!sInitialized) {
214                sSelectScale =
215                        Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale));
216                sDuration =
217                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));
218                sInitialized = true;
219            }
220        }
221
222        class HeaderFocusAnimator extends FocusAnimator {
223
224            ItemBridgeAdapter.ViewHolder mViewHolder;
225            HeaderFocusAnimator(View view, float scale, int duration) {
226                super(view, scale, false, duration);
227                mViewHolder = (ItemBridgeAdapter.ViewHolder) mGridView.getChildViewHolder(view);
228            }
229
230            @Override
231            void setFocusLevel(float level) {
232                Presenter presenter = mViewHolder.getPresenter();
233                if (presenter instanceof RowHeaderPresenter) {
234                    ((RowHeaderPresenter) presenter).setSelectLevel(
235                            ((RowHeaderPresenter.ViewHolder) mViewHolder.getViewHolder()), level);
236                }
237                super.setFocusLevel(level);
238            }
239
240        }
241
242        private void viewFocused(View view, boolean hasFocus) {
243            view.setSelected(hasFocus);
244            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
245            if (animator == null) {
246                animator = new HeaderFocusAnimator(view, sSelectScale, sDuration);
247                view.setTag(R.id.lb_focus_animator, animator);
248            }
249            animator.animateFocus(hasFocus, false);
250        }
251
252        @Override
253        public void onItemFocused(View view, boolean hasFocus) {
254            viewFocused(view, hasFocus);
255        }
256
257        @Override
258        public void onInitializeView(View view) {
259        }
260
261    }
262}
263