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