Home | History | Annotate | Download | only in radio
      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 {
     78         void onExitAnimationComplete();
     79     }
     80 
     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      */
    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      */
    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      */
    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