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