blob: 728d31fecc317053ad0b7528aff45b1310dc016a [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.support.v17.leanback.widget;
import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_LARGE;
import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_MEDIUM;
import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_NONE;
import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_SMALL;
import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_XSMALL;
import android.animation.TimeAnimator;
import android.content.res.Resources;
import android.support.v17.leanback.R;
import android.support.v17.leanback.graphics.ColorOverlayDimmer;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewParent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
/**
* Sets up the highlighting behavior when an item gains focus.
*/
public class FocusHighlightHelper {
static boolean isValidZoomIndex(int zoomIndex) {
return zoomIndex == ZOOM_FACTOR_NONE || getResId(zoomIndex) > 0;
}
static int getResId(int zoomIndex) {
switch (zoomIndex) {
case ZOOM_FACTOR_SMALL:
return R.fraction.lb_focus_zoom_factor_small;
case ZOOM_FACTOR_XSMALL:
return R.fraction.lb_focus_zoom_factor_xsmall;
case ZOOM_FACTOR_MEDIUM:
return R.fraction.lb_focus_zoom_factor_medium;
case ZOOM_FACTOR_LARGE:
return R.fraction.lb_focus_zoom_factor_large;
default:
return 0;
}
}
static class FocusAnimator implements TimeAnimator.TimeListener {
private final View mView;
private final int mDuration;
private final ShadowOverlayContainer mWrapper;
private final float mScaleDiff;
private float mFocusLevel = 0f;
private float mFocusLevelStart;
private float mFocusLevelDelta;
private final TimeAnimator mAnimator = new TimeAnimator();
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final ColorOverlayDimmer mDimmer;
void animateFocus(boolean select, boolean immediate) {
endAnimation();
final float end = select ? 1 : 0;
if (immediate) {
setFocusLevel(end);
} else if (mFocusLevel != end) {
mFocusLevelStart = mFocusLevel;
mFocusLevelDelta = end - mFocusLevelStart;
mAnimator.start();
}
}
FocusAnimator(View view, float scale, boolean useDimmer, int duration) {
mView = view;
mDuration = duration;
mScaleDiff = scale - 1f;
if (view instanceof ShadowOverlayContainer) {
mWrapper = (ShadowOverlayContainer) view;
} else {
mWrapper = null;
}
mAnimator.setTimeListener(this);
if (useDimmer) {
mDimmer = ColorOverlayDimmer.createDefault(view.getContext());
} else {
mDimmer = null;
}
}
void setFocusLevel(float level) {
mFocusLevel = level;
float scale = 1f + mScaleDiff * level;
mView.setScaleX(scale);
mView.setScaleY(scale);
if (mWrapper != null) {
mWrapper.setShadowFocusLevel(level);
} else {
ShadowOverlayHelper.setNoneWrapperShadowFocusLevel(mView, level);
}
if (mDimmer != null) {
mDimmer.setActiveLevel(level);
int color = mDimmer.getPaint().getColor();
if (mWrapper != null) {
mWrapper.setOverlayColor(color);
} else {
ShadowOverlayHelper.setNoneWrapperOverlayColor(mView, color);
}
}
}
float getFocusLevel() {
return mFocusLevel;
}
void endAnimation() {
mAnimator.end();
}
@Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
float fraction;
if (totalTime >= mDuration) {
fraction = 1;
mAnimator.end();
} else {
fraction = (float) (totalTime / (double) mDuration);
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
}
}
static class BrowseItemFocusHighlight implements FocusHighlightHandler {
private static final int DURATION_MS = 150;
private int mScaleIndex;
private final boolean mUseDimmer;
BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer) {
if (!isValidZoomIndex(zoomIndex)) {
throw new IllegalArgumentException("Unhandled zoom index");
}
mScaleIndex = zoomIndex;
mUseDimmer = useDimmer;
}
private float getScale(Resources res) {
return mScaleIndex == ZOOM_FACTOR_NONE ? 1f :
res.getFraction(getResId(mScaleIndex), 1, 1);
}
@Override
public void onItemFocused(View view, boolean hasFocus) {
view.setSelected(hasFocus);
getOrCreateAnimator(view).animateFocus(hasFocus, false);
}
@Override
public void onInitializeView(View view) {
getOrCreateAnimator(view).animateFocus(false, true);
}
private FocusAnimator getOrCreateAnimator(View view) {
FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
if (animator == null) {
animator = new FocusAnimator(
view, getScale(view.getResources()), mUseDimmer, DURATION_MS);
view.setTag(R.id.lb_focus_animator, animator);
}
return animator;
}
}
/**
* Sets up the focus highlight behavior of a focused item in browse list row. App usually does
* not call this method, it uses {@link ListRowPresenter#ListRowPresenter(int, boolean)}.
*
* @param zoomIndex One of {@link FocusHighlight#ZOOM_FACTOR_SMALL}
* {@link FocusHighlight#ZOOM_FACTOR_XSMALL}
* {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}
* {@link FocusHighlight#ZOOM_FACTOR_LARGE}
* {@link FocusHighlight#ZOOM_FACTOR_NONE}.
* @param useDimmer Allow dimming browse item when unselected.
* @param adapter adapter of the list row.
*/
public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex,
boolean useDimmer) {
adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex, useDimmer));
}
/**
* Sets up default focus highlight behavior of a focused item in header list. It would scale
* the focused item and update
* {@link RowHeaderPresenter#onSelectLevelChanged(RowHeaderPresenter.ViewHolder)}.
* Equivalent to call setupHeaderItemFocusHighlight(gridView, true). This method should be
* called after header fragment onViewCreated().
*
* @param gridView The header list.
*/
public static void setupHeaderItemFocusHighlight(VerticalGridView gridView) {
setupHeaderItemFocusHighlight(gridView, true);
}
/**
* Sets up the focus highlight behavior of a focused item in header list. This method should be
* called after header fragment onViewCreated().
*
* @param gridView The header list.
* @param scaleEnabled True if scale the item when focused, false otherwise. Note that
* {@link RowHeaderPresenter#onSelectLevelChanged(RowHeaderPresenter.ViewHolder)}
* will always be called regardless value of scaleEnabled.
*/
public static void setupHeaderItemFocusHighlight(VerticalGridView gridView,
boolean scaleEnabled) {
if (gridView != null && gridView.getAdapter() instanceof ItemBridgeAdapter) {
((ItemBridgeAdapter) gridView.getAdapter())
.setFocusHighlight(new HeaderItemFocusHighlight(scaleEnabled));
}
}
static class HeaderItemFocusHighlight implements FocusHighlightHandler {
private boolean mInitialized;
private float mSelectScale;
private int mDuration;
boolean mScaleEnabled;
HeaderItemFocusHighlight(boolean scaleEnabled) {
mScaleEnabled = scaleEnabled;
}
void lazyInit(View view) {
if (!mInitialized) {
Resources res = view.getResources();
mSelectScale = mScaleEnabled
? Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale))
: 1f;
mDuration =
Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));
mInitialized = true;
}
}
class HeaderFocusAnimator extends FocusAnimator {
ItemBridgeAdapter.ViewHolder mViewHolder;
HeaderFocusAnimator(View view, float scale, int duration) {
super(view, scale, false, duration);
ViewParent parent = view.getParent();
while (parent != null) {
if (parent instanceof RecyclerView) {
break;
}
parent = parent.getParent();
}
if (parent != null) {
mViewHolder = (ItemBridgeAdapter.ViewHolder) ((RecyclerView) parent)
.getChildViewHolder(view);
}
}
@Override
void setFocusLevel(float level) {
Presenter presenter = mViewHolder.getPresenter();
if (presenter instanceof RowHeaderPresenter) {
((RowHeaderPresenter) presenter).setSelectLevel(
((RowHeaderPresenter.ViewHolder) mViewHolder.getViewHolder()), level);
}
super.setFocusLevel(level);
}
}
private void viewFocused(View view, boolean hasFocus) {
lazyInit(view);
view.setSelected(hasFocus);
FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
if (animator == null) {
animator = new HeaderFocusAnimator(view, mSelectScale, mDuration);
view.setTag(R.id.lb_focus_animator, animator);
}
animator.animateFocus(hasFocus, false);
}
@Override
public void onItemFocused(View view, boolean hasFocus) {
viewFocused(view, hasFocus);
}
@Override
public void onInitializeView(View view) {
}
}
}