blob: fa74ce3e175aaa7397b0849112f7d75a56710a06 [file] [log] [blame]
/*
* Copyright (C) 2015 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 com.android.tv.menu;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Outline;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.tv.R;
/**
* A base class to render a card.
*/
public abstract class BaseCardView<T> extends LinearLayout implements ItemListRowView.CardView<T> {
private static final String TAG = "BaseCardView";
private static final boolean DEBUG = false;
private static final float SCALE_FACTOR_0F = 0f;
private static final float SCALE_FACTOR_1F = 1f;
private ValueAnimator mFocusAnimator;
private final int mFocusAnimDuration;
private final float mFocusTranslationZ;
private final float mVerticalCardMargin;
private final float mCardCornerRadius;
private float mFocusAnimatedValue;
private boolean mExtendViewOnFocus;
private final float mExtendedCardHeight;
private final float mTextViewHeight;
private final float mExtendedTextViewHeight;
@Nullable
private TextView mTextView;
@Nullable
private TextView mTextViewFocused;
private final int mCardImageWidth;
private final float mCardHeight;
private boolean mSelected;
public BaseCardView(Context context) {
this(context, null);
}
public BaseCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setClipToOutline(true);
mFocusAnimDuration = getResources().getInteger(R.integer.menu_focus_anim_duration);
mFocusTranslationZ = getResources().getDimension(R.dimen.channel_card_elevation_focused)
- getResources().getDimension(R.dimen.card_elevation_normal);
mVerticalCardMargin = 2 * (
getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top)
+ getResources().getDimensionPixelOffset(R.dimen.menu_list_margin_top));
// Ensure the same elevation and focus animation for all subclasses.
setElevation(getResources().getDimension(R.dimen.card_elevation_normal));
mCardCornerRadius = getResources().getDimensionPixelSize(R.dimen.channel_card_round_radius);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius);
}
});
mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height);
mExtendedCardHeight = getResources().getDimensionPixelSize(
R.dimen.card_layout_height_extended);
mTextViewHeight = getResources().getDimensionPixelSize(R.dimen.card_meta_layout_height);
mExtendedTextViewHeight = getResources().getDimensionPixelOffset(
R.dimen.card_meta_layout_height_extended);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTextView = (TextView) findViewById(R.id.card_text);
mTextViewFocused = (TextView) findViewById(R.id.card_text_focused);
}
/**
* Called when the view is displayed.
*/
@Override
public void onBind(T item, boolean selected) {
setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
}
@Override
public void onRecycled() { }
@Override
public void onSelected() {
mSelected = true;
if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
startFocusAnimation(SCALE_FACTOR_1F);
} else {
cancelFocusAnimationIfAny();
setFocusAnimatedValue(SCALE_FACTOR_1F);
}
}
@Override
public void onDeselected() {
mSelected = false;
if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
startFocusAnimation(SCALE_FACTOR_0F);
} else {
cancelFocusAnimationIfAny();
setFocusAnimatedValue(SCALE_FACTOR_0F);
}
}
/**
* Sets text of this card view.
*/
public void setText(int resId) {
if (mTextViewFocused != null) {
mTextViewFocused.setText(resId);
}
if (mTextView != null) {
mTextView.setText(resId);
}
onTextViewUpdated();
}
/**
* Sets text of this card view.
*/
public void setText(String text) {
if (mTextViewFocused != null) {
mTextViewFocused.setText(text);
}
if (mTextView != null) {
mTextView.setText(text);
}
onTextViewUpdated();
}
private void onTextViewUpdated() {
if (mTextView != null && mTextViewFocused != null) {
mTextViewFocused.measure(
MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1;
if (mExtendViewOnFocus) {
setTextViewFocusedAlpha(mSelected ? 1f : 0f);
} else {
setTextViewFocusedAlpha(1f);
}
}
setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
}
/**
* Enables or disables text view of this card view.
*/
public void setTextViewEnabled(boolean enabled) {
if (mTextViewFocused != null) {
mTextViewFocused.setEnabled(enabled);
}
if (mTextView != null) {
mTextView.setEnabled(enabled);
}
}
/**
* Called when the focus animation started.
*/
protected void onFocusAnimationStart(boolean selected) {
if (mExtendViewOnFocus) {
setTextViewFocusedAlpha(selected ? 1f : 0f);
}
}
/**
* Called when the focus animation ended.
*/
protected void onFocusAnimationEnd(boolean selected) {
// do nothing.
}
/**
* Called when the view is bound, or while focus animation is running with a value
* between {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}.
*/
protected void onSetFocusAnimatedValue(float animatedValue) {
float cardViewHeight = (mExtendViewOnFocus && isFocused())
? mExtendedCardHeight : mCardHeight;
float scale = 1f + (mVerticalCardMargin / cardViewHeight) * animatedValue;
setScaleX(scale);
setScaleY(scale);
setTranslationZ(mFocusTranslationZ * animatedValue);
if (mTextView != null && mTextViewFocused != null) {
ViewGroup.LayoutParams params = mTextView.getLayoutParams();
int height = mExtendViewOnFocus ? Math.round(mTextViewHeight
+ (mExtendedTextViewHeight - mTextViewHeight) * animatedValue)
: (int) mTextViewHeight;
if (height != params.height) {
params.height = height;
setTextViewLayoutParams(params);
}
if (mExtendViewOnFocus) {
setTextViewFocusedAlpha(animatedValue);
}
}
}
private void setFocusAnimatedValue(float animatedValue) {
mFocusAnimatedValue = animatedValue;
onSetFocusAnimatedValue(animatedValue);
}
private void startFocusAnimation(final float targetAnimatedValue) {
cancelFocusAnimationIfAny();
final boolean selected = targetAnimatedValue == SCALE_FACTOR_1F;
mFocusAnimator = ValueAnimator.ofFloat(mFocusAnimatedValue, targetAnimatedValue);
mFocusAnimator.setDuration(mFocusAnimDuration);
mFocusAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setHasTransientState(true);
onFocusAnimationStart(selected);
}
@Override
public void onAnimationEnd(Animator animation) {
setHasTransientState(false);
onFocusAnimationEnd(selected);
}
});
mFocusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setFocusAnimatedValue((Float) animation.getAnimatedValue());
}
});
mFocusAnimator.start();
}
private void cancelFocusAnimationIfAny() {
if (mFocusAnimator != null) {
mFocusAnimator.cancel();
mFocusAnimator = null;
}
}
private void setTextViewLayoutParams(ViewGroup.LayoutParams params) {
mTextViewFocused.setLayoutParams(params);
mTextView.setLayoutParams(params);
}
private void setTextViewFocusedAlpha(float focusedAlpha) {
mTextViewFocused.setAlpha(focusedAlpha);
mTextView.setAlpha(1f - focusedAlpha);
}
}