| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.car.radio; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.animation.ValueAnimator; |
| import android.annotation.NonNull; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Point; |
| import android.support.v4.view.animation.FastOutSlowInInterpolator; |
| import android.support.v7.widget.CardView; |
| import android.view.Display; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewTreeObserver; |
| import android.view.WindowManager; |
| |
| import androidx.car.utils.ColumnCalculator; |
| |
| /** |
| * A animation manager that is responsible for the start and exiting animation for the |
| * {@link RadioPresetsFragment}. |
| */ |
| public class RadioAnimationManager { |
| private static final int START_ANIM_DURATION_MS = 500; |
| private static final int START_TRANSLATE_ANIM_DELAY_MS = 117; |
| private static final int START_TRANSLATE_ANIM_DURATION_MS = 383; |
| private static final int START_FADE_ANIM_DELAY_MS = 150; |
| private static final int START_FADE_ANIM_DURATION_MS = 100; |
| |
| private static final int STOP_ANIM_DELAY_MS = 215; |
| private static final int STOP_ANIM_DURATION_MS = 333; |
| private static final int STOP_TRANSLATE_ANIM_DURATION_MS = 417; |
| private static final int STOP_FADE_ANIM_DELAY_MS = 150; |
| private static final int STOP_FADE_ANIM_DURATION_MS = 100; |
| |
| private static final FastOutSlowInInterpolator sInterpolator = new FastOutSlowInInterpolator(); |
| |
| private final Context mContext; |
| private final int mScreenWidth; |
| private int mAppScreenHeight; |
| |
| private final int mCardColumnSpan; |
| private final int mCornerRadius; |
| private final int mActionPanelHeight; |
| private final int mPresetFinalHeight; |
| |
| private final int mFabSize; |
| private final int mPresetFabSize; |
| private final int mPresetContainerHeight; |
| |
| private final View mContainer; |
| private final CardView mRadioCard; |
| private final View mRadioCardContainer; |
| private final View mFab; |
| private final View mPresetFab; |
| private final View mRadioControls; |
| private final View mRadioCardControls; |
| private final View mPresetsList; |
| |
| public interface OnExitCompleteListener { |
| void onExitAnimationComplete(); |
| } |
| |
| public RadioAnimationManager(Context context, View container) { |
| mContext = context; |
| mContainer = container; |
| |
| WindowManager windowManager = |
| (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); |
| Display display = windowManager.getDefaultDisplay(); |
| Point size = new Point(); |
| display.getSize(size); |
| mScreenWidth = size.x; |
| |
| Resources res = mContext.getResources(); |
| mCardColumnSpan = res.getInteger(R.integer.column_card_default_column_span); |
| mCornerRadius = res.getDimensionPixelSize(R.dimen.car_preset_item_radius); |
| mActionPanelHeight = res.getDimensionPixelSize(R.dimen.car_action_bar_height); |
| mPresetFinalHeight = res.getDimensionPixelSize(R.dimen.car_preset_item_height); |
| mFabSize = res.getDimensionPixelSize(R.dimen.car_radio_controls_fab_size); |
| mPresetFabSize = res.getDimensionPixelSize(R.dimen.car_presets_play_button_size); |
| mPresetContainerHeight = res.getDimensionPixelSize(R.dimen.car_preset_container_height); |
| |
| mRadioCard = container.findViewById(R.id.current_radio_station_card); |
| |
| mRadioCardContainer = container.findViewById(R.id.preset_current_card_container); |
| mFab = container.findViewById(R.id.radio_play_button); |
| mPresetFab = container.findViewById(R.id.preset_radio_play_button); |
| mRadioControls = container.findViewById(R.id.radio_buttons_container); |
| mRadioCardControls = container.findViewById(R.id.current_radio_station_card_controls); |
| mPresetsList = container.findViewById(R.id.presets_list); |
| } |
| |
| /** |
| * Start the exit animation for the preset activity. This animation will move the radio controls |
| * down to where it would be in the {@link CarRadioActivity}. Upon completion of the |
| * animation, the given {@link OnExitCompleteListener} will be notified. |
| */ |
| public void playExitAnimation(@NonNull OnExitCompleteListener listener) { |
| // Animator that will animate the radius of mRadioCard from rounded to non-rounded. |
| ValueAnimator cornerRadiusAnimator = ValueAnimator.ofInt(mCornerRadius, 0); |
| cornerRadiusAnimator.setStartDelay(STOP_ANIM_DELAY_MS); |
| cornerRadiusAnimator.setDuration(STOP_ANIM_DURATION_MS); |
| cornerRadiusAnimator.addUpdateListener( |
| animator -> mRadioCard.setRadius((int) animator.getAnimatedValue())); |
| cornerRadiusAnimator.setInterpolator(sInterpolator); |
| |
| // Animator that will animate the radius of mRadioCard from its current width to the width |
| // of the screen. |
| ValueAnimator widthAnimator = ValueAnimator.ofInt(mRadioCard.getWidth(), mScreenWidth); |
| widthAnimator.setInterpolator(sInterpolator); |
| widthAnimator.setStartDelay(STOP_ANIM_DELAY_MS); |
| widthAnimator.setDuration(STOP_ANIM_DURATION_MS); |
| widthAnimator.addUpdateListener(valueAnimator -> { |
| int width = (int) valueAnimator.getAnimatedValue(); |
| mRadioCard.getLayoutParams().width = width; |
| mRadioCard.requestLayout(); |
| }); |
| |
| // Animate the height of the radio controls from its current height to the full height of |
| // the action panel. |
| ValueAnimator heightAnimator = ValueAnimator.ofInt(mRadioCard.getHeight(), |
| mActionPanelHeight); |
| heightAnimator.setInterpolator(sInterpolator); |
| heightAnimator.setStartDelay(STOP_ANIM_DELAY_MS); |
| heightAnimator.setDuration(STOP_ANIM_DURATION_MS); |
| heightAnimator.addUpdateListener(valueAnimator -> { |
| int height = (int) valueAnimator.getAnimatedValue(); |
| mRadioCard.getLayoutParams().height = height; |
| mRadioCard.requestLayout(); |
| }); |
| |
| // Animate the fab back to the size it will be in the main radio display. |
| ValueAnimator fabAnimator = ValueAnimator.ofInt(mPresetFabSize, mFabSize); |
| fabAnimator.setInterpolator(sInterpolator); |
| fabAnimator.setStartDelay(STOP_ANIM_DELAY_MS); |
| fabAnimator.setDuration(STOP_ANIM_DURATION_MS); |
| fabAnimator.addUpdateListener(valueAnimator -> { |
| int fabSize = (int) valueAnimator.getAnimatedValue(); |
| ViewGroup.LayoutParams layoutParams = mFab.getLayoutParams(); |
| layoutParams.width = fabSize; |
| layoutParams.height = fabSize; |
| mFab.requestLayout(); |
| |
| layoutParams = mPresetFab.getLayoutParams(); |
| layoutParams.width = fabSize; |
| layoutParams.height = fabSize; |
| mPresetFab.requestLayout(); |
| }); |
| |
| // The animator for the move downwards of the radio card. |
| ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(mRadioCard, |
| View.TRANSLATION_Y, mRadioCard.getTranslationY(), 0); |
| translationYAnimator.setDuration(STOP_TRANSLATE_ANIM_DURATION_MS); |
| |
| // The animator for the move downwards of the preset list. |
| ObjectAnimator presetAnimator = ObjectAnimator.ofFloat(mPresetsList, |
| View.TRANSLATION_Y, 0, mAppScreenHeight); |
| presetAnimator.setDuration(STOP_TRANSLATE_ANIM_DURATION_MS); |
| presetAnimator.start(); |
| |
| // The animator for will fade in the radio controls. |
| ValueAnimator radioControlsAlphaAnimator = ValueAnimator.ofFloat(0.f, 1.f); |
| radioControlsAlphaAnimator.setInterpolator(sInterpolator); |
| radioControlsAlphaAnimator.setStartDelay(STOP_FADE_ANIM_DELAY_MS); |
| radioControlsAlphaAnimator.setDuration(STOP_FADE_ANIM_DURATION_MS); |
| radioControlsAlphaAnimator.addUpdateListener(valueAnimator -> |
| mRadioControls.setAlpha((float) valueAnimator.getAnimatedValue())); |
| radioControlsAlphaAnimator.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animator) { |
| mRadioControls.setVisibility(View.VISIBLE); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animator) {} |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| }); |
| |
| // The animator for will fade out of the preset radio controls. |
| ObjectAnimator radioCardControlsAlphaAnimator = ObjectAnimator.ofFloat(mRadioCardControls, |
| View.ALPHA, 1.f, 0.f); |
| radioCardControlsAlphaAnimator.setInterpolator(sInterpolator); |
| radioCardControlsAlphaAnimator.setStartDelay(STOP_FADE_ANIM_DELAY_MS); |
| radioCardControlsAlphaAnimator.setDuration(STOP_FADE_ANIM_DURATION_MS); |
| radioCardControlsAlphaAnimator.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animator) {} |
| |
| @Override |
| public void onAnimationEnd(Animator animator) { |
| mRadioCardControls.setVisibility(View.GONE); |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| }); |
| |
| AnimatorSet animatorSet = new AnimatorSet(); |
| animatorSet.playTogether(cornerRadiusAnimator, heightAnimator, widthAnimator, fabAnimator, |
| translationYAnimator, radioControlsAlphaAnimator, radioCardControlsAlphaAnimator, |
| presetAnimator); |
| animatorSet.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animator) { |
| // Remove any elevation from the radio container since the radio card will move |
| // out from the container. |
| mRadioCardContainer.setElevation(0); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animator) { |
| listener.onExitAnimationComplete(); |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| }); |
| animatorSet.start(); |
| } |
| |
| /** |
| * Start the enter animation for the preset fragment. This animation will move the radio |
| * controls up from where they are in the {@link CarRadioActivity} to its final position. |
| */ |
| public void playEnterAnimation() { |
| // The animation requires that we know the size of the activity window. This value is |
| // different from the size of the screen, which we could obtain using DisplayMetrics. As a |
| // result, need to use a ViewTreeObserver to get the size of the containing view. |
| mContainer.getViewTreeObserver().addOnGlobalLayoutListener( |
| new ViewTreeObserver.OnGlobalLayoutListener() { |
| @Override |
| public void onGlobalLayout() { |
| mAppScreenHeight = mContainer.getHeight(); |
| startEnterAnimation(); |
| mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this); |
| } |
| }); |
| } |
| |
| /** |
| * Actually starts the animations for the enter of the preset fragment. |
| */ |
| private void startEnterAnimation() { |
| FastOutSlowInInterpolator sInterpolator = new FastOutSlowInInterpolator(); |
| |
| // Animator that will animate the radius of mRadioCard to its final rounded state. |
| ValueAnimator cornerRadiusAnimator = ValueAnimator.ofInt(0, mCornerRadius); |
| cornerRadiusAnimator.setDuration(START_ANIM_DURATION_MS); |
| cornerRadiusAnimator.addUpdateListener( |
| animator -> mRadioCard.setRadius((int) animator.getAnimatedValue())); |
| cornerRadiusAnimator.setInterpolator(sInterpolator); |
| |
| // Animate the radio card from the size of the screen to its final size in the preset |
| // list. |
| ValueAnimator widthAnimator = ValueAnimator.ofInt(mScreenWidth, |
| ColumnCalculator.getInstance(mContext).getSizeForColumnSpan(mCardColumnSpan)); |
| widthAnimator.setInterpolator(sInterpolator); |
| widthAnimator.setDuration(START_ANIM_DURATION_MS); |
| widthAnimator.addUpdateListener(valueAnimator -> { |
| int width = (int) valueAnimator.getAnimatedValue(); |
| mRadioCard.getLayoutParams().width = width; |
| mRadioCard.requestLayout(); |
| }); |
| |
| // Shrink the radio card down to its final height. |
| ValueAnimator heightAnimator = ValueAnimator.ofInt(mActionPanelHeight, mPresetFinalHeight); |
| heightAnimator.setInterpolator(sInterpolator); |
| heightAnimator.setDuration(START_ANIM_DURATION_MS); |
| heightAnimator.addUpdateListener(valueAnimator -> { |
| int height = (int) valueAnimator.getAnimatedValue(); |
| mRadioCard.getLayoutParams().height = height; |
| mRadioCard.requestLayout(); |
| }); |
| |
| // Animate the fab from its large size in the radio controls to the smaller size in the |
| // preset list. |
| ValueAnimator fabAnimator = ValueAnimator.ofInt(mFabSize, mPresetFabSize); |
| fabAnimator.setInterpolator(sInterpolator); |
| fabAnimator.setDuration(START_ANIM_DURATION_MS); |
| fabAnimator.addUpdateListener(valueAnimator -> { |
| int fabSize = (int) valueAnimator.getAnimatedValue(); |
| ViewGroup.LayoutParams layoutParams = mFab.getLayoutParams(); |
| layoutParams.width = fabSize; |
| layoutParams.height = fabSize; |
| mFab.requestLayout(); |
| |
| layoutParams = mPresetFab.getLayoutParams(); |
| layoutParams.width = fabSize; |
| layoutParams.height = fabSize; |
| mPresetFab.requestLayout(); |
| }); |
| |
| // The top of the screen relative to where mRadioCard is positioned. |
| int topOfScreen = mAppScreenHeight - mActionPanelHeight; |
| |
| // Because the height of the radio controls changes, we need to add the difference in height |
| // to the final translation. |
| topOfScreen = topOfScreen + (mActionPanelHeight - mPresetFinalHeight); |
| |
| // The radio card will need to be centered within the area given by mPresetContainerHeight. |
| // This finalTranslation value is negative so that mRadioCard moves upwards. |
| int finalTranslation = -(topOfScreen - ((mPresetContainerHeight - mPresetFinalHeight) / 2)); |
| |
| // Animator to move the radio card from the bottom of the screen to its final y value. |
| ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(mRadioCard, |
| View.TRANSLATION_Y, 0, finalTranslation); |
| translationYAnimator.setStartDelay(START_TRANSLATE_ANIM_DELAY_MS); |
| translationYAnimator.setDuration(START_TRANSLATE_ANIM_DURATION_MS); |
| |
| // Animator to slide the preset list from the bottom of the screen to just below the radio |
| // card. |
| ObjectAnimator presetAnimator = ObjectAnimator.ofFloat(mPresetsList, |
| View.TRANSLATION_Y, mAppScreenHeight, 0); |
| presetAnimator.setStartDelay(START_TRANSLATE_ANIM_DELAY_MS); |
| presetAnimator.setDuration(START_TRANSLATE_ANIM_DURATION_MS); |
| presetAnimator.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animator) { |
| mPresetsList.setVisibility(View.VISIBLE); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animator) {} |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| }); |
| |
| // Animator to fade out the radio controls. |
| ValueAnimator radioControlsAlphaAnimator = ValueAnimator.ofFloat(1.f, 0.f); |
| radioControlsAlphaAnimator.setInterpolator(sInterpolator); |
| radioControlsAlphaAnimator.setStartDelay(START_FADE_ANIM_DELAY_MS); |
| radioControlsAlphaAnimator.setDuration(START_FADE_ANIM_DURATION_MS); |
| radioControlsAlphaAnimator.addUpdateListener(valueAnimator -> |
| mRadioControls.setAlpha((float) valueAnimator.getAnimatedValue())); |
| radioControlsAlphaAnimator.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animator) {} |
| |
| @Override |
| public void onAnimationEnd(Animator animator) { |
| mRadioControls.setVisibility(View.GONE); |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| }); |
| |
| // Animator to fade in the radio controls for the preset card. |
| ObjectAnimator radioCardControlsAlphaAnimator = ObjectAnimator.ofFloat(mRadioCardControls, |
| View.ALPHA, 0.f, 1.f); |
| radioCardControlsAlphaAnimator.setInterpolator(sInterpolator); |
| radioCardControlsAlphaAnimator.setStartDelay(START_FADE_ANIM_DELAY_MS); |
| radioCardControlsAlphaAnimator.setDuration(START_FADE_ANIM_DURATION_MS); |
| radioCardControlsAlphaAnimator.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animator) { |
| mRadioCardControls.setVisibility(View.VISIBLE); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animator) {} |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| }); |
| |
| AnimatorSet animatorSet = new AnimatorSet(); |
| animatorSet.playTogether(cornerRadiusAnimator, heightAnimator, widthAnimator, fabAnimator, |
| translationYAnimator, radioControlsAlphaAnimator, radioCardControlsAlphaAnimator, |
| presetAnimator); |
| animatorSet.start(); |
| } |
| } |