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