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