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