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