• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
14 package android.support.v17.leanback.widget;
15 
16 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_LARGE;
17 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_MEDIUM;
18 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_NONE;
19 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_SMALL;
20 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_XSMALL;
21 
22 import android.animation.TimeAnimator;
23 import android.content.res.Resources;
24 import android.support.v17.leanback.R;
25 import android.support.v17.leanback.app.HeadersFragment;
26 import android.support.v17.leanback.graphics.ColorOverlayDimmer;
27 import android.support.v7.widget.RecyclerView;
28 import android.util.TypedValue;
29 import android.view.View;
30 import android.view.ViewParent;
31 import android.view.animation.AccelerateDecelerateInterpolator;
32 import android.view.animation.Interpolator;
33 
34 /**
35  * Sets up the highlighting behavior when an item gains focus.
36  */
37 public class FocusHighlightHelper {
38 
isValidZoomIndex(int zoomIndex)39     static boolean isValidZoomIndex(int zoomIndex) {
40         return zoomIndex == ZOOM_FACTOR_NONE || getResId(zoomIndex) > 0;
41     }
42 
getResId(int zoomIndex)43     static int getResId(int zoomIndex) {
44         switch (zoomIndex) {
45             case ZOOM_FACTOR_SMALL:
46                 return R.fraction.lb_focus_zoom_factor_small;
47             case ZOOM_FACTOR_XSMALL:
48                 return R.fraction.lb_focus_zoom_factor_xsmall;
49             case ZOOM_FACTOR_MEDIUM:
50                 return R.fraction.lb_focus_zoom_factor_medium;
51             case ZOOM_FACTOR_LARGE:
52                 return R.fraction.lb_focus_zoom_factor_large;
53             default:
54                 return 0;
55         }
56     }
57 
58 
59     static class FocusAnimator implements TimeAnimator.TimeListener {
60         private final View mView;
61         private final int mDuration;
62         private final ShadowOverlayContainer mWrapper;
63         private final float mScaleDiff;
64         private float mFocusLevel = 0f;
65         private float mFocusLevelStart;
66         private float mFocusLevelDelta;
67         private final TimeAnimator mAnimator = new TimeAnimator();
68         private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
69         private final ColorOverlayDimmer mDimmer;
70 
animateFocus(boolean select, boolean immediate)71         void animateFocus(boolean select, boolean immediate) {
72             endAnimation();
73             final float end = select ? 1 : 0;
74             if (immediate) {
75                 setFocusLevel(end);
76             } else if (mFocusLevel != end) {
77                 mFocusLevelStart = mFocusLevel;
78                 mFocusLevelDelta = end - mFocusLevelStart;
79                 mAnimator.start();
80             }
81         }
82 
FocusAnimator(View view, float scale, boolean useDimmer, int duration)83         FocusAnimator(View view, float scale, boolean useDimmer, int duration) {
84             mView = view;
85             mDuration = duration;
86             mScaleDiff = scale - 1f;
87             if (view instanceof ShadowOverlayContainer) {
88                 mWrapper = (ShadowOverlayContainer) view;
89             } else {
90                 mWrapper = null;
91             }
92             mAnimator.setTimeListener(this);
93             if (useDimmer) {
94                 mDimmer = ColorOverlayDimmer.createDefault(view.getContext());
95             } else {
96                 mDimmer = null;
97             }
98         }
99 
setFocusLevel(float level)100         void setFocusLevel(float level) {
101             mFocusLevel = level;
102             float scale = 1f + mScaleDiff * level;
103             mView.setScaleX(scale);
104             mView.setScaleY(scale);
105             if (mWrapper != null) {
106                 mWrapper.setShadowFocusLevel(level);
107             } else {
108                 ShadowOverlayHelper.setNoneWrapperShadowFocusLevel(mView, level);
109             }
110             if (mDimmer != null) {
111                 mDimmer.setActiveLevel(level);
112                 int color = mDimmer.getPaint().getColor();
113                 if (mWrapper != null) {
114                     mWrapper.setOverlayColor(color);
115                 } else {
116                     ShadowOverlayHelper.setNoneWrapperOverlayColor(mView, color);
117                 }
118             }
119         }
120 
getFocusLevel()121         float getFocusLevel() {
122             return mFocusLevel;
123         }
124 
endAnimation()125         void endAnimation() {
126             mAnimator.end();
127         }
128 
129         @Override
onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime)130         public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
131             float fraction;
132             if (totalTime >= mDuration) {
133                 fraction = 1;
134                 mAnimator.end();
135             } else {
136                 fraction = (float) (totalTime / (double) mDuration);
137             }
138             if (mInterpolator != null) {
139                 fraction = mInterpolator.getInterpolation(fraction);
140             }
141             setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
142         }
143     }
144 
145     static class BrowseItemFocusHighlight implements FocusHighlightHandler {
146         private static final int DURATION_MS = 150;
147 
148         private int mScaleIndex;
149         private final boolean mUseDimmer;
150 
BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer)151         BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer) {
152             if (!isValidZoomIndex(zoomIndex)) {
153                 throw new IllegalArgumentException("Unhandled zoom index");
154             }
155             mScaleIndex = zoomIndex;
156             mUseDimmer = useDimmer;
157         }
158 
getScale(Resources res)159         private float getScale(Resources res) {
160             return mScaleIndex == ZOOM_FACTOR_NONE ? 1f :
161                     res.getFraction(getResId(mScaleIndex), 1, 1);
162         }
163 
164         @Override
onItemFocused(View view, boolean hasFocus)165         public void onItemFocused(View view, boolean hasFocus) {
166             view.setSelected(hasFocus);
167             getOrCreateAnimator(view).animateFocus(hasFocus, false);
168         }
169 
170         @Override
onInitializeView(View view)171         public void onInitializeView(View view) {
172             getOrCreateAnimator(view).animateFocus(false, true);
173         }
174 
getOrCreateAnimator(View view)175         private FocusAnimator getOrCreateAnimator(View view) {
176             FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
177             if (animator == null) {
178                 animator = new FocusAnimator(
179                         view, getScale(view.getResources()), mUseDimmer, DURATION_MS);
180                 view.setTag(R.id.lb_focus_animator, animator);
181             }
182             return animator;
183         }
184 
185     }
186 
187     /**
188      * Sets up the focus highlight behavior of a focused item in browse list row. App usually does
189      * not call this method, it uses {@link ListRowPresenter#ListRowPresenter(int, boolean)}.
190      *
191      * @param zoomIndex One of {@link FocusHighlight#ZOOM_FACTOR_SMALL}
192      * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}
193      * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}
194      * {@link FocusHighlight#ZOOM_FACTOR_LARGE}
195      * {@link FocusHighlight#ZOOM_FACTOR_NONE}.
196      * @param useDimmer Allow dimming browse item when unselected.
197      * @param adapter  adapter of the list row.
198      */
setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex, boolean useDimmer)199     public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex,
200             boolean useDimmer) {
201         adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex, useDimmer));
202     }
203 
204     /**
205      * Sets up default focus highlight behavior of a focused item in header list. It would scale
206      * the focused item and update
207      * {@link RowHeaderPresenter#onSelectLevelChanged(RowHeaderPresenter.ViewHolder)}.
208      * Equivalent to call setupHeaderItemFocusHighlight(gridView, true).
209      *
210      * @param gridView  The header list.
211      * @deprecated Use {@link #setupHeaderItemFocusHighlight(ItemBridgeAdapter)}
212      */
213     @Deprecated
setupHeaderItemFocusHighlight(VerticalGridView gridView)214     public static void setupHeaderItemFocusHighlight(VerticalGridView gridView) {
215         setupHeaderItemFocusHighlight(gridView, true);
216     }
217 
218     /**
219      * Sets up the focus highlight behavior of a focused item in header list.
220      *
221      * @param gridView  The header list.
222      * @param scaleEnabled True if scale the item when focused, false otherwise. Note that
223      * {@link RowHeaderPresenter#onSelectLevelChanged(RowHeaderPresenter.ViewHolder)}
224      * will always be called regardless value of scaleEnabled.
225      * @deprecated Use {@link #setupHeaderItemFocusHighlight(ItemBridgeAdapter, boolean)}
226      */
227     @Deprecated
setupHeaderItemFocusHighlight(VerticalGridView gridView, boolean scaleEnabled)228     public static void setupHeaderItemFocusHighlight(VerticalGridView gridView,
229                                                      boolean scaleEnabled) {
230         if (gridView != null && gridView.getAdapter() instanceof ItemBridgeAdapter) {
231             ((ItemBridgeAdapter) gridView.getAdapter())
232                     .setFocusHighlight(new HeaderItemFocusHighlight(scaleEnabled));
233         }
234     }
235 
236     /**
237      * Sets up default focus highlight behavior of a focused item in header list. It would scale
238      * the focused item and update
239      * {@link RowHeaderPresenter#onSelectLevelChanged(RowHeaderPresenter.ViewHolder)}.
240      * Equivalent to call setupHeaderItemFocusHighlight(itemBridgeAdapter, true).
241      *
242      * @param adapter  The adapter of HeadersFragment.
243      * @see {@link HeadersFragment#getBridgeAdapter()}
244      */
setupHeaderItemFocusHighlight(ItemBridgeAdapter adapter)245     public static void setupHeaderItemFocusHighlight(ItemBridgeAdapter adapter) {
246         setupHeaderItemFocusHighlight(adapter, true);
247     }
248 
249     /**
250      * Sets up the focus highlight behavior of a focused item in header list.
251      *
252      * @param adapter  The adapter of HeadersFragment.
253      * @param scaleEnabled True if scale the item when focused, false otherwise. Note that
254      * {@link RowHeaderPresenter#onSelectLevelChanged(RowHeaderPresenter.ViewHolder)}
255      * will always be called regardless value of scaleEnabled.
256      * @see {@link HeadersFragment#getBridgeAdapter()}
257      */
setupHeaderItemFocusHighlight(ItemBridgeAdapter adapter, boolean scaleEnabled)258     public static void setupHeaderItemFocusHighlight(ItemBridgeAdapter adapter,
259             boolean scaleEnabled) {
260         adapter.setFocusHighlight(new HeaderItemFocusHighlight(scaleEnabled));
261     }
262 
263     static class HeaderItemFocusHighlight implements FocusHighlightHandler {
264         private boolean mInitialized;
265         private float mSelectScale;
266         private int mDuration;
267         boolean mScaleEnabled;
268 
HeaderItemFocusHighlight(boolean scaleEnabled)269         HeaderItemFocusHighlight(boolean scaleEnabled) {
270             mScaleEnabled = scaleEnabled;
271         }
272 
lazyInit(View view)273         void lazyInit(View view) {
274             if (!mInitialized) {
275                 Resources res = view.getResources();
276                 TypedValue value = new TypedValue();
277                 if (mScaleEnabled) {
278                     res.getValue(R.dimen.lb_browse_header_select_scale, value, true);
279                     mSelectScale = value.getFloat();
280                 } else {
281                     mSelectScale = 1f;
282                 }
283                 res.getValue(R.dimen.lb_browse_header_select_duration, value, true);
284                 mDuration = value.data;
285                 mInitialized = true;
286             }
287         }
288 
289         static class HeaderFocusAnimator extends FocusAnimator {
290 
291             ItemBridgeAdapter.ViewHolder mViewHolder;
HeaderFocusAnimator(View view, float scale, int duration)292             HeaderFocusAnimator(View view, float scale, int duration) {
293                 super(view, scale, false, duration);
294 
295                 ViewParent parent = view.getParent();
296                 while (parent != null) {
297                     if (parent instanceof RecyclerView) {
298                         break;
299                     }
300                     parent = parent.getParent();
301                 }
302                 if (parent != null) {
303                     mViewHolder = (ItemBridgeAdapter.ViewHolder) ((RecyclerView) parent)
304                             .getChildViewHolder(view);
305                 }
306             }
307 
308             @Override
setFocusLevel(float level)309             void setFocusLevel(float level) {
310                 Presenter presenter = mViewHolder.getPresenter();
311                 if (presenter instanceof RowHeaderPresenter) {
312                     ((RowHeaderPresenter) presenter).setSelectLevel(
313                             ((RowHeaderPresenter.ViewHolder) mViewHolder.getViewHolder()), level);
314                 }
315                 super.setFocusLevel(level);
316             }
317 
318         }
319 
viewFocused(View view, boolean hasFocus)320         private void viewFocused(View view, boolean hasFocus) {
321             lazyInit(view);
322             view.setSelected(hasFocus);
323             FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
324             if (animator == null) {
325                 animator = new HeaderFocusAnimator(view, mSelectScale, mDuration);
326                 view.setTag(R.id.lb_focus_animator, animator);
327             }
328             animator.animateFocus(hasFocus, false);
329         }
330 
331         @Override
onItemFocused(View view, boolean hasFocus)332         public void onItemFocused(View view, boolean hasFocus) {
333             viewFocused(view, hasFocus);
334         }
335 
336         @Override
onInitializeView(View view)337         public void onInitializeView(View view) {
338         }
339 
340     }
341 }
342