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