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