• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.support.annotation.Nullable;
25 import android.text.TextUtils;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.ViewOutlineProvider;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import com.android.tv.R;
34 
35 /**
36  * A base class to render a card.
37  */
38 public abstract class BaseCardView<T> extends LinearLayout implements ItemListRowView.CardView<T> {
39     private static final float SCALE_FACTOR_0F = 0f;
40     private static final float SCALE_FACTOR_1F = 1f;
41 
42     private ValueAnimator mFocusAnimator;
43     private final int mFocusAnimDuration;
44     private final float mFocusTranslationZ;
45     private final float mVerticalCardMargin;
46     private final float mCardCornerRadius;
47     private float mFocusAnimatedValue;
48     private boolean mExtendViewOnFocus;
49     private final float mExtendedCardHeight;
50     private final float mTextViewHeight;
51     private final float mExtendedTextViewHeight;
52     @Nullable
53     private TextView mTextView;
54     @Nullable
55     private TextView mTextViewFocused;
56     private final int mCardImageWidth;
57     private final float mCardHeight;
58     private boolean mSelected;
59 
60     private int mTextResId;
61     private String mTextString;
62     private boolean mTextChanged;
63 
BaseCardView(Context context)64     public BaseCardView(Context context) {
65         this(context, null);
66     }
67 
BaseCardView(Context context, AttributeSet attrs)68     public BaseCardView(Context context, AttributeSet attrs) {
69         this(context, attrs, 0);
70     }
71 
BaseCardView(Context context, AttributeSet attrs, int defStyle)72     public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
73         super(context, attrs, defStyle);
74 
75         setClipToOutline(true);
76         mFocusAnimDuration = getResources().getInteger(R.integer.menu_focus_anim_duration);
77         mFocusTranslationZ = getResources().getDimension(R.dimen.channel_card_elevation_focused)
78                 - getResources().getDimension(R.dimen.card_elevation_normal);
79         mVerticalCardMargin = 2 * (
80                 getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top)
81                 + getResources().getDimensionPixelOffset(R.dimen.menu_list_margin_top));
82         // Ensure the same elevation and focus animation for all subclasses.
83         setElevation(getResources().getDimension(R.dimen.card_elevation_normal));
84         mCardCornerRadius = getResources().getDimensionPixelSize(R.dimen.channel_card_round_radius);
85         setOutlineProvider(new ViewOutlineProvider() {
86             @Override
87             public void getOutline(View view, Outline outline) {
88                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius);
89             }
90         });
91         mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
92         mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height);
93         mExtendedCardHeight = getResources().getDimensionPixelSize(
94                 R.dimen.card_layout_height_extended);
95         mTextViewHeight = getResources().getDimensionPixelSize(R.dimen.card_meta_layout_height);
96         mExtendedTextViewHeight = getResources().getDimensionPixelOffset(
97                 R.dimen.card_meta_layout_height_extended);
98     }
99 
100     @Override
onFinishInflate()101     protected void onFinishInflate() {
102         super.onFinishInflate();
103         mTextView = (TextView) findViewById(R.id.card_text);
104         mTextViewFocused = (TextView) findViewById(R.id.card_text_focused);
105     }
106 
107     /**
108      * Called when the view is displayed.
109      */
110     @Override
onBind(T item, boolean selected)111     public void onBind(T item, boolean selected) {
112         setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
113     }
114 
115     @Override
onRecycled()116     public void onRecycled() { }
117 
118     @Override
onSelected()119     public void onSelected() {
120         mSelected = true;
121         if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
122             startFocusAnimation(SCALE_FACTOR_1F);
123         } else {
124             cancelFocusAnimationIfAny();
125             setFocusAnimatedValue(SCALE_FACTOR_1F);
126         }
127     }
128 
129     @Override
onDeselected()130     public void onDeselected() {
131         mSelected = false;
132         if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
133             startFocusAnimation(SCALE_FACTOR_0F);
134         } else {
135             cancelFocusAnimationIfAny();
136             setFocusAnimatedValue(SCALE_FACTOR_0F);
137         }
138     }
139 
140     /**
141      * Sets text of this card view.
142      */
setText(int resId)143     public void setText(int resId) {
144         if (mTextResId != resId) {
145             mTextResId = resId;
146             mTextString = null;
147             mTextChanged = true;
148             if (mTextViewFocused != null) {
149                 mTextViewFocused.setText(resId);
150             }
151             if (mTextView != null) {
152                 mTextView.setText(resId);
153             }
154             onTextViewUpdated();
155         }
156     }
157 
158     /**
159      * Sets text of this card view.
160      */
setText(String text)161     public void setText(String text) {
162         if (!TextUtils.equals(text, mTextString)) {
163             mTextString = text;
164             mTextResId = 0;
165             mTextChanged = true;
166             if (mTextViewFocused != null) {
167                 mTextViewFocused.setText(text);
168             }
169             if (mTextView != null) {
170                 mTextView.setText(text);
171             }
172             onTextViewUpdated();
173         }
174     }
175 
onTextViewUpdated()176     private void onTextViewUpdated() {
177         if (mTextView != null && mTextViewFocused != null) {
178             mTextViewFocused.measure(
179                 MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
180                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
181             mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1;
182             if (mExtendViewOnFocus) {
183                 setTextViewFocusedAlpha(mSelected ? 1f : 0f);
184             } else {
185                 setTextViewFocusedAlpha(1f);
186             }
187         }
188         setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
189     }
190 
191     /**
192      * Enables or disables text view of this card view.
193      */
setTextViewEnabled(boolean enabled)194     public void setTextViewEnabled(boolean enabled) {
195         if (mTextViewFocused != null) {
196             mTextViewFocused.setEnabled(enabled);
197         }
198         if (mTextView != null) {
199             mTextView.setEnabled(enabled);
200         }
201     }
202 
203     /**
204      * Called when the focus animation started.
205      */
onFocusAnimationStart(boolean selected)206     protected void onFocusAnimationStart(boolean selected) {
207         if (mExtendViewOnFocus) {
208             setTextViewFocusedAlpha(selected ? 1f : 0f);
209         }
210     }
211 
212     /**
213      * Called when the focus animation ended.
214      */
onFocusAnimationEnd(boolean selected)215     protected void onFocusAnimationEnd(boolean selected) {
216         // do nothing.
217     }
218 
219     /**
220      * Called when the view is bound, or while focus animation is running with a value
221      * between {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}.
222      */
onSetFocusAnimatedValue(float animatedValue)223     protected void onSetFocusAnimatedValue(float animatedValue) {
224         float cardViewHeight = (mExtendViewOnFocus && isFocused())
225                 ? mExtendedCardHeight : mCardHeight;
226         float scale = 1f + (mVerticalCardMargin / cardViewHeight) * animatedValue;
227         setScaleX(scale);
228         setScaleY(scale);
229         setTranslationZ(mFocusTranslationZ * animatedValue);
230         if (mTextView != null && mTextViewFocused != null) {
231             ViewGroup.LayoutParams params = mTextView.getLayoutParams();
232             int height = mExtendViewOnFocus ? Math.round(mTextViewHeight
233                 + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue)
234                 : (int) mTextViewHeight;
235             if (height != params.height) {
236                 params.height = height;
237                 setTextViewLayoutParams(params);
238             }
239             if (mExtendViewOnFocus) {
240                 setTextViewFocusedAlpha(animatedValue);
241             }
242         }
243     }
244 
setFocusAnimatedValue(float animatedValue)245     private void setFocusAnimatedValue(float animatedValue) {
246         mFocusAnimatedValue = animatedValue;
247         onSetFocusAnimatedValue(animatedValue);
248     }
249 
startFocusAnimation(final float targetAnimatedValue)250     private void startFocusAnimation(final float targetAnimatedValue) {
251         cancelFocusAnimationIfAny();
252         final boolean selected = targetAnimatedValue == SCALE_FACTOR_1F;
253         mFocusAnimator = ValueAnimator.ofFloat(mFocusAnimatedValue, targetAnimatedValue);
254         mFocusAnimator.setDuration(mFocusAnimDuration);
255         mFocusAnimator.addListener(new AnimatorListenerAdapter() {
256             @Override
257             public void onAnimationStart(Animator animation) {
258                 setHasTransientState(true);
259                 onFocusAnimationStart(selected);
260             }
261 
262             @Override
263             public void onAnimationEnd(Animator animation) {
264                 setHasTransientState(false);
265                 onFocusAnimationEnd(selected);
266             }
267         });
268         mFocusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
269             @Override
270             public void onAnimationUpdate(ValueAnimator animation) {
271                 setFocusAnimatedValue((Float) animation.getAnimatedValue());
272             }
273         });
274         mFocusAnimator.start();
275     }
276 
cancelFocusAnimationIfAny()277     private void cancelFocusAnimationIfAny() {
278         if (mFocusAnimator != null) {
279             mFocusAnimator.cancel();
280             mFocusAnimator = null;
281         }
282     }
283 
setTextViewLayoutParams(ViewGroup.LayoutParams params)284     private void setTextViewLayoutParams(ViewGroup.LayoutParams params) {
285         mTextViewFocused.setLayoutParams(params);
286         mTextView.setLayoutParams(params);
287     }
288 
setTextViewFocusedAlpha(float focusedAlpha)289     private void setTextViewFocusedAlpha(float focusedAlpha) {
290         mTextViewFocused.setAlpha(focusedAlpha);
291         mTextView.setAlpha(1f - focusedAlpha);
292     }
293 }
294