Home | History | Annotate | Download | only in menu
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.menu;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ValueAnimator;
     22 import android.content.Context;
     23 import android.graphics.Outline;
     24 import android.util.AttributeSet;
     25 import android.view.View;
     26 import android.view.ViewOutlineProvider;
     27 import android.widget.LinearLayout;
     28 
     29 import com.android.tv.R;
     30 
     31 /**
     32  * A base class to render a card.
     33  */
     34 public abstract class BaseCardView<T> extends LinearLayout implements ItemListRowView.CardView<T> {
     35     private static final String TAG = "BaseCardView";
     36     private static final boolean DEBUG = false;
     37 
     38     private static final float SCALE_FACTOR_0F = 0f;
     39     private static final float SCALE_FACTOR_1F = 1f;
     40 
     41     private ValueAnimator mFocusAnimator;
     42     private final int mFocusAnimDuration;
     43     private final float mFocusTranslationZ;
     44     private final float mVerticalCardMargin;
     45     private final float mCardCornerRadius;
     46     private float mFocusAnimatedValue;
     47 
     48     public BaseCardView(Context context) {
     49         this(context, null);
     50     }
     51 
     52     public BaseCardView(Context context, AttributeSet attrs) {
     53         this(context, attrs, 0);
     54     }
     55 
     56     public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
     57         super(context, attrs, defStyle);
     58 
     59         setClipToOutline(true);
     60         mFocusAnimDuration = getResources().getInteger(R.integer.menu_focus_anim_duration);
     61         mFocusTranslationZ = getResources().getDimension(R.dimen.channel_card_elevation_focused)
     62                 - getResources().getDimension(R.dimen.card_elevation_normal);
     63         mVerticalCardMargin = 2 * (
     64                 getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top)
     65                 + getResources().getDimensionPixelOffset(R.dimen.menu_list_margin_top));
     66         // Ensure the same elevation and focus animation for all subclasses.
     67         setElevation(getResources().getDimension(R.dimen.card_elevation_normal));
     68         mCardCornerRadius = getResources().getDimensionPixelSize(R.dimen.channel_card_round_radius);
     69         setOutlineProvider(new ViewOutlineProvider() {
     70             @Override
     71             public void getOutline(View view, Outline outline) {
     72                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius);
     73             }
     74         });
     75     }
     76 
     77     /**
     78      * Called when the view is displayed.
     79      */
     80     @Override
     81     public void onBind(T item, boolean selected) {
     82         // Note that getCardHeight() will be called by setFocusAnimatedValue().
     83         // Therefore, be sure that getCardHeight() has a proper value before this method is called.
     84        setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
     85     }
     86 
     87     @Override
     88     public void onRecycled() { }
     89 
     90     @Override
     91     public void onSelected() {
     92         if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
     93             startFocusAnimation(SCALE_FACTOR_1F);
     94         } else {
     95             cancelFocusAnimationIfAny();
     96             setFocusAnimatedValue(SCALE_FACTOR_1F);
     97         }
     98     }
     99 
    100     @Override
    101     public void onDeselected() {
    102         if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
    103             startFocusAnimation(SCALE_FACTOR_0F);
    104         } else {
    105             cancelFocusAnimationIfAny();
    106             setFocusAnimatedValue(SCALE_FACTOR_0F);
    107         }
    108     }
    109 
    110     /**
    111      * Called when the focus animation started.
    112      */
    113     protected void onFocusAnimationStart(boolean selected) {
    114         // do nothing.
    115     }
    116 
    117     /**
    118      * Called when the focus animation ended.
    119      */
    120     protected void onFocusAnimationEnd(boolean selected) {
    121         // do nothing.
    122     }
    123 
    124     /**
    125      * Called when the view is bound, or while focus animation is running with a value
    126      * between {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}.
    127      */
    128     protected void onSetFocusAnimatedValue(float animatedValue) {
    129         float scale = 1f + (mVerticalCardMargin / getCardHeight()) * animatedValue;
    130         setScaleX(scale);
    131         setScaleY(scale);
    132         setTranslationZ(mFocusTranslationZ * animatedValue);
    133     }
    134 
    135     private void setFocusAnimatedValue(float animatedValue) {
    136         mFocusAnimatedValue = animatedValue;
    137         onSetFocusAnimatedValue(animatedValue);
    138     }
    139 
    140     private void startFocusAnimation(final float targetAnimatedValue) {
    141         cancelFocusAnimationIfAny();
    142         final boolean selected = targetAnimatedValue == SCALE_FACTOR_1F;
    143         mFocusAnimator = ValueAnimator.ofFloat(mFocusAnimatedValue, targetAnimatedValue);
    144         mFocusAnimator.setDuration(mFocusAnimDuration);
    145         mFocusAnimator.addListener(new AnimatorListenerAdapter() {
    146             @Override
    147             public void onAnimationStart(Animator animation) {
    148                 setHasTransientState(true);
    149                 onFocusAnimationStart(selected);
    150             }
    151 
    152             @Override
    153             public void onAnimationEnd(Animator animation) {
    154                 setHasTransientState(false);
    155                 onFocusAnimationEnd(selected);
    156             }
    157         });
    158         mFocusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    159             @Override
    160             public void onAnimationUpdate(ValueAnimator animation) {
    161                 setFocusAnimatedValue((Float) animation.getAnimatedValue());
    162             }
    163         });
    164         mFocusAnimator.start();
    165     }
    166 
    167     private void cancelFocusAnimationIfAny() {
    168         if (mFocusAnimator != null) {
    169             mFocusAnimator.cancel();
    170             mFocusAnimator = null;
    171         }
    172     }
    173 
    174     /**
    175      * The implementation should return the height of the card.
    176      */
    177     protected abstract float getCardHeight();
    178 }
    179