• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.car.radio;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.Point;
26 import android.support.annotation.NonNull;
27 import android.support.v4.view.animation.FastOutSlowInInterpolator;
28 import android.support.v7.widget.CardView;
29 import android.view.Display;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.ViewTreeObserver;
33 import android.view.WindowManager;
34 import com.android.car.stream.ui.ColumnCalculator;
35 
36 /**
37  * A animation manager that is responsible for the start and exiting animation for the
38  * {@link RadioPresetsFragment}.
39  */
40 public class RadioAnimationManager {
41     private static final int START_ANIM_DURATION_MS = 500;
42     private static final int START_TRANSLATE_ANIM_DELAY_MS = 117;
43     private static final int START_TRANSLATE_ANIM_DURATION_MS = 383;
44     private static final int START_FADE_ANIM_DELAY_MS = 150;
45     private static final int START_FADE_ANIM_DURATION_MS = 100;
46 
47     private static final int STOP_ANIM_DELAY_MS = 215;
48     private static final int STOP_ANIM_DURATION_MS = 333;
49     private static final int STOP_TRANSLATE_ANIM_DURATION_MS = 417;
50     private static final int STOP_FADE_ANIM_DELAY_MS = 150;
51     private static final int STOP_FADE_ANIM_DURATION_MS = 100;
52 
53     private static final FastOutSlowInInterpolator sInterpolator = new FastOutSlowInInterpolator();
54 
55     private final Context mContext;
56     private final int mScreenWidth;
57     private int mAppScreenHeight;
58 
59     private final int mCardColumnSpan;
60     private final int mCornerRadius;
61     private final int mActionPanelHeight;
62     private final int mPresetFinalHeight;
63 
64     private final int mFabSize;
65     private final int mPresetFabSize;
66     private final int mPresetContainerHeight;
67 
68     private final View mContainer;
69     private final CardView mRadioCard;
70     private final View mRadioCardContainer;
71     private final View mFab;
72     private final View mPresetFab;
73     private final View mRadioControls;
74     private final View mRadioCardControls;
75     private final View mPresetsList;
76 
77     public interface OnExitCompleteListener {
onExitAnimationComplete()78         void onExitAnimationComplete();
79     }
80 
RadioAnimationManager(Context context, View container)81     public RadioAnimationManager(Context context, View container) {
82         mContext = context;
83         mContainer = container;
84 
85         WindowManager windowManager =
86                 (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
87         Display display = windowManager.getDefaultDisplay();
88         Point size = new Point();
89         display.getSize(size);
90         mScreenWidth = size.x;
91 
92         Resources res = mContext.getResources();
93         mCardColumnSpan = res.getInteger(R.integer.stream_card_default_column_span);
94         mCornerRadius = res.getDimensionPixelSize(R.dimen.car_preset_item_radius);
95         mActionPanelHeight = res.getDimensionPixelSize(R.dimen.action_panel_height);
96         mPresetFinalHeight = res.getDimensionPixelSize(R.dimen.car_preset_item_height);
97         mFabSize = res.getDimensionPixelSize(R.dimen.stream_fab_size);
98         mPresetFabSize = res.getDimensionPixelSize(R.dimen.car_presets_play_button_size);
99         mPresetContainerHeight = res.getDimensionPixelSize(R.dimen.car_preset_container_height);
100 
101         mRadioCard = (CardView) container.findViewById(R.id.current_radio_station_card);
102         mRadioCardContainer = container.findViewById(R.id.preset_current_card_container);
103         mFab = container.findViewById(R.id.radio_play_button);
104         mPresetFab = container.findViewById(R.id.preset_radio_play_button);
105         mRadioControls = container.findViewById(R.id.radio_buttons_container);
106         mRadioCardControls = container.findViewById(R.id.current_radio_station_card_controls);
107         mPresetsList = container.findViewById(R.id.presets_list);
108     }
109 
110     /**
111      * Start the exit animation for the preset activity. This animation will move the radio controls
112      * down to where it would be in the {@link CarRadioActivity}. Upon completion of the
113      * animation, the given {@link OnExitCompleteListener} will be notified.
114      */
playExitAnimation(@onNull OnExitCompleteListener listener)115     public void playExitAnimation(@NonNull OnExitCompleteListener listener) {
116         // Animator that will animate the radius of mRadioCard from rounded to non-rounded.
117         ValueAnimator cornerRadiusAnimator = ValueAnimator.ofInt(mCornerRadius, 0);
118         cornerRadiusAnimator.setStartDelay(STOP_ANIM_DELAY_MS);
119         cornerRadiusAnimator.setDuration(STOP_ANIM_DURATION_MS);
120         cornerRadiusAnimator.addUpdateListener(
121                 animator -> mRadioCard.setRadius((int) animator.getAnimatedValue()));
122         cornerRadiusAnimator.setInterpolator(sInterpolator);
123 
124         // Animator that will animate the radius of mRadioCard from its current width to the width
125         // of the screen.
126         ValueAnimator widthAnimator = ValueAnimator.ofInt(mRadioCard.getWidth(), mScreenWidth);
127         widthAnimator.setInterpolator(sInterpolator);
128         widthAnimator.setStartDelay(STOP_ANIM_DELAY_MS);
129         widthAnimator.setDuration(STOP_ANIM_DURATION_MS);
130         widthAnimator.addUpdateListener(valueAnimator -> {
131             int width = (int) valueAnimator.getAnimatedValue();
132             mRadioCard.getLayoutParams().width  = width;
133             mRadioCard.requestLayout();
134         });
135 
136         // Animate the height of the radio controls from its current height to the full height of
137         // the action panel.
138         ValueAnimator heightAnimator = ValueAnimator.ofInt(mRadioCard.getHeight(),
139                 mActionPanelHeight);
140         heightAnimator.setInterpolator(sInterpolator);
141         heightAnimator.setStartDelay(STOP_ANIM_DELAY_MS);
142         heightAnimator.setDuration(STOP_ANIM_DURATION_MS);
143         heightAnimator.addUpdateListener(valueAnimator -> {
144             int height = (int) valueAnimator.getAnimatedValue();
145             mRadioCard.getLayoutParams().height = height;
146             mRadioCard.requestLayout();
147         });
148 
149         // Animate the fab back to the size it will be in the main radio display.
150         ValueAnimator fabAnimator = ValueAnimator.ofInt(mPresetFabSize, mFabSize);
151         fabAnimator.setInterpolator(sInterpolator);
152         fabAnimator.setStartDelay(STOP_ANIM_DELAY_MS);
153         fabAnimator.setDuration(STOP_ANIM_DURATION_MS);
154         fabAnimator.addUpdateListener(valueAnimator -> {
155             int fabSize = (int) valueAnimator.getAnimatedValue();
156             ViewGroup.LayoutParams layoutParams = mFab.getLayoutParams();
157             layoutParams.width = fabSize;
158             layoutParams.height = fabSize;
159             mFab.requestLayout();
160 
161             layoutParams = mPresetFab.getLayoutParams();
162             layoutParams.width = fabSize;
163             layoutParams.height = fabSize;
164             mPresetFab.requestLayout();
165         });
166 
167         // The animator for the move downwards of the radio card.
168         ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(mRadioCard,
169                 View.TRANSLATION_Y, mRadioCard.getTranslationY(), 0);
170         translationYAnimator.setDuration(STOP_TRANSLATE_ANIM_DURATION_MS);
171 
172         // The animator for the move downwards of the preset list.
173         ObjectAnimator presetAnimator = ObjectAnimator.ofFloat(mPresetsList,
174                 View.TRANSLATION_Y, 0, mAppScreenHeight);
175         presetAnimator.setDuration(STOP_TRANSLATE_ANIM_DURATION_MS);
176         presetAnimator.start();
177 
178         // The animator for will fade in the radio controls.
179         ValueAnimator radioControlsAlphaAnimator = ValueAnimator.ofFloat(0.f, 1.f);
180         radioControlsAlphaAnimator.setInterpolator(sInterpolator);
181         radioControlsAlphaAnimator.setStartDelay(STOP_FADE_ANIM_DELAY_MS);
182         radioControlsAlphaAnimator.setDuration(STOP_FADE_ANIM_DURATION_MS);
183         radioControlsAlphaAnimator.addUpdateListener(valueAnimator ->
184                 mRadioControls.setAlpha((float) valueAnimator.getAnimatedValue()));
185         radioControlsAlphaAnimator.addListener(new Animator.AnimatorListener() {
186             @Override
187             public void onAnimationStart(Animator animator) {
188                 mRadioControls.setVisibility(View.VISIBLE);
189             }
190 
191             @Override
192             public void onAnimationEnd(Animator animator) {}
193 
194             @Override
195             public void onAnimationCancel(Animator animator) {}
196 
197             @Override
198             public void onAnimationRepeat(Animator animator) {}
199         });
200 
201         // The animator for will fade out of the preset radio controls.
202         ObjectAnimator radioCardControlsAlphaAnimator = ObjectAnimator.ofFloat(mRadioCardControls,
203                 View.ALPHA, 1.f, 0.f);
204         radioCardControlsAlphaAnimator.setInterpolator(sInterpolator);
205         radioCardControlsAlphaAnimator.setStartDelay(STOP_FADE_ANIM_DELAY_MS);
206         radioCardControlsAlphaAnimator.setDuration(STOP_FADE_ANIM_DURATION_MS);
207         radioCardControlsAlphaAnimator.addListener(new Animator.AnimatorListener() {
208             @Override
209             public void onAnimationStart(Animator animator) {}
210 
211             @Override
212             public void onAnimationEnd(Animator animator) {
213                 mRadioCardControls.setVisibility(View.GONE);
214             }
215 
216             @Override
217             public void onAnimationCancel(Animator animator) {}
218 
219             @Override
220             public void onAnimationRepeat(Animator animator) {}
221         });
222 
223         AnimatorSet animatorSet = new AnimatorSet();
224         animatorSet.playTogether(cornerRadiusAnimator, heightAnimator, widthAnimator, fabAnimator,
225                 translationYAnimator, radioControlsAlphaAnimator, radioCardControlsAlphaAnimator,
226                 presetAnimator);
227         animatorSet.addListener(new Animator.AnimatorListener() {
228             @Override
229             public void onAnimationStart(Animator animator) {
230                 // Remove any elevation from the radio container since the radio card will move
231                 // out from the container.
232                 mRadioCardContainer.setElevation(0);
233             }
234 
235             @Override
236             public void onAnimationEnd(Animator animator) {
237                 listener.onExitAnimationComplete();
238             }
239 
240             @Override
241             public void onAnimationCancel(Animator animator) {}
242 
243             @Override
244             public void onAnimationRepeat(Animator animator) {}
245         });
246         animatorSet.start();
247     }
248 
249     /**
250      * Start the enter animation for the preset fragment. This animation will move the radio
251      * controls up from where they are in the {@link CarRadioActivity} to its final position.
252      */
playEnterAnimation()253     public void playEnterAnimation() {
254         // The animation requires that we know the size of the activity window. This value is
255         // different from the size of the screen, which we could obtain using DisplayMetrics. As a
256         // result, need to use a ViewTreeObserver to get the size of the containing view.
257         mContainer.getViewTreeObserver().addOnGlobalLayoutListener(
258                 new ViewTreeObserver.OnGlobalLayoutListener() {
259                     @Override
260                     public void onGlobalLayout() {
261                         mAppScreenHeight = mContainer.getHeight();
262                         startEnterAnimation();
263                         mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
264                     }
265                 });
266     }
267 
268     /**
269      * Actually starts the animations for the enter of the preset fragment.
270      */
startEnterAnimation()271     private void startEnterAnimation() {
272         FastOutSlowInInterpolator sInterpolator = new FastOutSlowInInterpolator();
273 
274         // Animator that will animate the radius of mRadioCard to its final rounded state.
275         ValueAnimator cornerRadiusAnimator = ValueAnimator.ofInt(0, mCornerRadius);
276         cornerRadiusAnimator.setDuration(START_ANIM_DURATION_MS);
277         cornerRadiusAnimator.addUpdateListener(
278                 animator -> mRadioCard.setRadius((int) animator.getAnimatedValue()));
279         cornerRadiusAnimator.setInterpolator(sInterpolator);
280 
281         // Animate the radio card from the size of the screen to its final size in the preset
282         // list.
283         ValueAnimator widthAnimator = ValueAnimator.ofInt(mScreenWidth,
284                 ColumnCalculator.getInstance(mContext).getSizeForColumnSpan(mCardColumnSpan));
285         widthAnimator.setInterpolator(sInterpolator);
286         widthAnimator.setDuration(START_ANIM_DURATION_MS);
287         widthAnimator.addUpdateListener(valueAnimator -> {
288             int width = (int) valueAnimator.getAnimatedValue();
289             mRadioCard.getLayoutParams().width  = width;
290             mRadioCard.requestLayout();
291         });
292 
293         // Shrink the radio card down to its final height.
294         ValueAnimator heightAnimator = ValueAnimator.ofInt(mActionPanelHeight, mPresetFinalHeight);
295         heightAnimator.setInterpolator(sInterpolator);
296         heightAnimator.setDuration(START_ANIM_DURATION_MS);
297         heightAnimator.addUpdateListener(valueAnimator -> {
298             int height = (int) valueAnimator.getAnimatedValue();
299             mRadioCard.getLayoutParams().height = height;
300             mRadioCard.requestLayout();
301         });
302 
303         // Animate the fab from its large size in the radio controls to the smaller size in the
304         // preset list.
305         ValueAnimator fabAnimator = ValueAnimator.ofInt(mFabSize, mPresetFabSize);
306         fabAnimator.setInterpolator(sInterpolator);
307         fabAnimator.setDuration(START_ANIM_DURATION_MS);
308         fabAnimator.addUpdateListener(valueAnimator -> {
309             int fabSize = (int) valueAnimator.getAnimatedValue();
310             ViewGroup.LayoutParams layoutParams = mFab.getLayoutParams();
311             layoutParams.width = fabSize;
312             layoutParams.height = fabSize;
313             mFab.requestLayout();
314 
315             layoutParams = mPresetFab.getLayoutParams();
316             layoutParams.width = fabSize;
317             layoutParams.height = fabSize;
318             mPresetFab.requestLayout();
319         });
320 
321         // The top of the screen relative to where mRadioCard is positioned.
322         int topOfScreen = mAppScreenHeight - mActionPanelHeight;
323 
324         // Because the height of the radio controls changes, we need to add the difference in height
325         // to the final translation.
326         topOfScreen = topOfScreen + (mActionPanelHeight - mPresetFinalHeight);
327 
328         // The radio card will need to be centered within the area given by mPresetContainerHeight.
329         // This finalTranslation value is negative so that mRadioCard moves upwards.
330         int finalTranslation = -(topOfScreen - ((mPresetContainerHeight - mPresetFinalHeight) / 2));
331 
332         // Animator to move the radio card from the bottom of the screen to its final y value.
333         ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(mRadioCard,
334                 View.TRANSLATION_Y, 0, finalTranslation);
335         translationYAnimator.setStartDelay(START_TRANSLATE_ANIM_DELAY_MS);
336         translationYAnimator.setDuration(START_TRANSLATE_ANIM_DURATION_MS);
337 
338         // Animator to slide the preset list from the bottom of the screen to just below the radio
339         // card.
340         ObjectAnimator presetAnimator = ObjectAnimator.ofFloat(mPresetsList,
341                 View.TRANSLATION_Y, mAppScreenHeight, 0);
342         presetAnimator.setStartDelay(START_TRANSLATE_ANIM_DELAY_MS);
343         presetAnimator.setDuration(START_TRANSLATE_ANIM_DURATION_MS);
344         presetAnimator.addListener(new Animator.AnimatorListener() {
345             @Override
346             public void onAnimationStart(Animator animator) {
347                 mPresetsList.setVisibility(View.VISIBLE);
348             }
349 
350             @Override
351             public void onAnimationEnd(Animator animator) {}
352 
353             @Override
354             public void onAnimationCancel(Animator animator) {}
355 
356             @Override
357             public void onAnimationRepeat(Animator animator) {}
358         });
359 
360         // Animator to fade out the radio controls.
361         ValueAnimator radioControlsAlphaAnimator = ValueAnimator.ofFloat(1.f, 0.f);
362         radioControlsAlphaAnimator.setInterpolator(sInterpolator);
363         radioControlsAlphaAnimator.setStartDelay(START_FADE_ANIM_DELAY_MS);
364         radioControlsAlphaAnimator.setDuration(START_FADE_ANIM_DURATION_MS);
365         radioControlsAlphaAnimator.addUpdateListener(valueAnimator ->
366                 mRadioControls.setAlpha((float) valueAnimator.getAnimatedValue()));
367         radioControlsAlphaAnimator.addListener(new Animator.AnimatorListener() {
368             @Override
369             public void onAnimationStart(Animator animator) {}
370 
371             @Override
372             public void onAnimationEnd(Animator animator) {
373                 mRadioControls.setVisibility(View.GONE);
374             }
375 
376             @Override
377             public void onAnimationCancel(Animator animator) {}
378 
379             @Override
380             public void onAnimationRepeat(Animator animator) {}
381         });
382 
383         // Animator to fade in the radio controls for the preset card.
384         ObjectAnimator radioCardControlsAlphaAnimator = ObjectAnimator.ofFloat(mRadioCardControls,
385                 View.ALPHA, 0.f, 1.f);
386         radioCardControlsAlphaAnimator.setInterpolator(sInterpolator);
387         radioCardControlsAlphaAnimator.setStartDelay(START_FADE_ANIM_DELAY_MS);
388         radioCardControlsAlphaAnimator.setDuration(START_FADE_ANIM_DURATION_MS);
389         radioCardControlsAlphaAnimator.addListener(new Animator.AnimatorListener() {
390             @Override
391             public void onAnimationStart(Animator animator) {
392                 mRadioCardControls.setVisibility(View.VISIBLE);
393             }
394 
395             @Override
396             public void onAnimationEnd(Animator animator) {}
397 
398             @Override
399             public void onAnimationCancel(Animator animator) {}
400 
401             @Override
402             public void onAnimationRepeat(Animator animator) {}
403         });
404 
405         AnimatorSet animatorSet = new AnimatorSet();
406         animatorSet.playTogether(cornerRadiusAnimator, heightAnimator, widthAnimator, fabAnimator,
407                 translationYAnimator, radioControlsAlphaAnimator, radioCardControlsAlphaAnimator,
408                 presetAnimator);
409         animatorSet.start();
410     }
411 }
412