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