1 /* 2 * Copyright (C) 2015 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.launcher3; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.animation.TimeInterpolator; 25 import android.animation.ValueAnimator; 26 import android.content.res.Resources; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.animation.AccelerateInterpolator; 30 31 import com.android.launcher3.allapps.AllAppsContainerView; 32 import com.android.launcher3.allapps.AllAppsTransitionController; 33 import com.android.launcher3.anim.AnimationLayerSet; 34 import com.android.launcher3.config.FeatureFlags; 35 import com.android.launcher3.util.CircleRevealOutlineProvider; 36 import com.android.launcher3.util.Thunk; 37 import com.android.launcher3.widget.WidgetsContainerView; 38 39 /** 40 * TODO: figure out what kind of tests we can write for this 41 * 42 * Things to test when changing the following class. 43 * - Home from workspace 44 * - from center screen 45 * - from other screens 46 * - Home from all apps 47 * - from center screen 48 * - from other screens 49 * - Back from all apps 50 * - from center screen 51 * - from other screens 52 * - Launch app from workspace and quit 53 * - with back 54 * - with home 55 * - Launch app from all apps and quit 56 * - with back 57 * - with home 58 * - Go to a screen that's not the default, then all 59 * apps, and launch and app, and go back 60 * - with back 61 * -with home 62 * - On workspace, long press power and go back 63 * - with back 64 * - with home 65 * - On all apps, long press power and go back 66 * - with back 67 * - with home 68 * - On workspace, power off 69 * - On all apps, power off 70 * - Launch an app and turn off the screen while in that app 71 * - Go back with home key 72 * - Go back with back key TODO: make this not go to workspace 73 * - From all apps 74 * - From workspace 75 * - Enter and exit car mode (becuase it causes an extra configuration changed) 76 * - From all apps 77 * - From the center workspace 78 * - From another workspace 79 */ 80 public class LauncherStateTransitionAnimation { 81 82 /** 83 * animation used for all apps and widget tray when 84 *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false} 85 */ 86 public static final int CIRCULAR_REVEAL = 0; 87 /** 88 * animation used for all apps and not widget tray when 89 *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true} 90 */ 91 public static final int PULLUP = 1; 92 93 private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f; 94 95 /** 96 * Private callbacks made during transition setup. 97 */ 98 private static class PrivateTransitionCallbacks { 99 private final float materialRevealViewFinalAlpha; 100 101 PrivateTransitionCallbacks(float revealAlpha) { 102 materialRevealViewFinalAlpha = revealAlpha; 103 } 104 105 float getMaterialRevealViewStartFinalRadius() { 106 return 0; 107 } 108 AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, 109 View buttonView) { 110 return null; 111 } 112 void onTransitionComplete() {} 113 } 114 115 public static final String TAG = "LSTAnimation"; 116 117 public static final int SINGLE_FRAME_DELAY = 16; 118 119 @Thunk Launcher mLauncher; 120 @Thunk AnimatorSet mCurrentAnimation; 121 AllAppsTransitionController mAllAppsController; 122 123 public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) { 124 mLauncher = l; 125 mAllAppsController = allAppsController; 126 } 127 128 /** 129 * Starts an animation to the apps view. 130 * 131 * @param startSearchAfterTransition Immediately starts app search after the transition to 132 * All Apps is completed. 133 */ 134 public void startAnimationToAllApps( 135 final boolean animated, final boolean startSearchAfterTransition) { 136 final AllAppsContainerView toView = mLauncher.getAppsView(); 137 final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation(); 138 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 139 @Override 140 public float getMaterialRevealViewStartFinalRadius() { 141 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 142 return allAppsButtonSize / 2; 143 } 144 @Override 145 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 146 final View revealView, final View allAppsButtonView) { 147 return new AnimatorListenerAdapter() { 148 public void onAnimationStart(Animator animation) { 149 allAppsButtonView.setVisibility(View.INVISIBLE); 150 } 151 public void onAnimationEnd(Animator animation) { 152 allAppsButtonView.setVisibility(View.VISIBLE); 153 } 154 }; 155 } 156 @Override 157 void onTransitionComplete() { 158 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 159 if (startSearchAfterTransition) { 160 toView.startAppsSearch(); 161 } 162 } 163 }; 164 int animType = CIRCULAR_REVEAL; 165 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 166 animType = PULLUP; 167 } 168 // Only animate the search bar if animating from spring loaded mode back to all apps 169 startAnimationToOverlay( 170 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb); 171 } 172 173 /** 174 * Starts an animation to the widgets view. 175 */ 176 public void startAnimationToWidgets(final boolean animated) { 177 final WidgetsContainerView toView = mLauncher.getWidgetsView(); 178 final View buttonView = mLauncher.getWidgetsButton(); 179 startAnimationToOverlay( 180 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL, 181 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){ 182 @Override 183 void onTransitionComplete() { 184 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 185 } 186 }); 187 } 188 189 /** 190 * Starts an animation to the workspace from the current overlay view. 191 */ 192 public void startAnimationToWorkspace(final Launcher.State fromState, 193 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 194 final boolean animated, final Runnable onCompleteRunnable) { 195 if (toWorkspaceState != Workspace.State.NORMAL && 196 toWorkspaceState != Workspace.State.SPRING_LOADED && 197 toWorkspaceState != Workspace.State.OVERVIEW) { 198 Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); 199 } 200 201 if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED 202 || mAllAppsController.isTransitioning()) { 203 int animType = CIRCULAR_REVEAL; 204 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 205 animType = PULLUP; 206 } 207 startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, 208 animated, animType, onCompleteRunnable); 209 } else if (fromState == Launcher.State.WIDGETS || 210 fromState == Launcher.State.WIDGETS_SPRING_LOADED) { 211 startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, 212 animated, onCompleteRunnable); 213 } else { 214 startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, 215 animated, onCompleteRunnable); 216 } 217 } 218 219 /** 220 * Creates and starts a new animation to a particular overlay view. 221 */ 222 private void startAnimationToOverlay( 223 final Workspace.State toWorkspaceState, 224 final View buttonView, final BaseContainerView toView, 225 final boolean animated, int animType, final PrivateTransitionCallbacks pCb) { 226 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 227 final Resources res = mLauncher.getResources(); 228 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 229 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 230 231 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 232 233 final AnimationLayerSet layerViews = new AnimationLayerSet(); 234 235 // If for some reason our views aren't initialized, don't animate 236 boolean initialized = buttonView != null; 237 238 // Cancel the current animation 239 cancelAnimation(); 240 241 final View contentView = toView.getContentView(); 242 playCommonTransitionAnimations(toWorkspaceState, 243 animated, initialized, animation, layerViews); 244 if (!animated || !initialized) { 245 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 246 toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 247 mAllAppsController.finishPullUp(); 248 } 249 toView.setTranslationX(0.0f); 250 toView.setTranslationY(0.0f); 251 toView.setScaleX(1.0f); 252 toView.setScaleY(1.0f); 253 toView.setAlpha(1.0f); 254 toView.setVisibility(View.VISIBLE); 255 256 // Show the content view 257 contentView.setVisibility(View.VISIBLE); 258 pCb.onTransitionComplete(); 259 return; 260 } 261 if (animType == CIRCULAR_REVEAL) { 262 // Setup the reveal view animation 263 final View revealView = toView.getRevealView(); 264 265 int width = revealView.getMeasuredWidth(); 266 int height = revealView.getMeasuredHeight(); 267 float revealRadius = (float) Math.hypot(width / 2, height / 2); 268 revealView.setVisibility(View.VISIBLE); 269 revealView.setAlpha(0f); 270 revealView.setTranslationY(0f); 271 revealView.setTranslationX(0f); 272 273 // Calculate the final animation values 274 int[] buttonViewToPanelDelta = 275 Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); 276 final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 277 final float revealViewToXDrift = buttonViewToPanelDelta[0]; 278 final float revealViewToYDrift = buttonViewToPanelDelta[1]; 279 280 // Create the animators 281 PropertyValuesHolder panelAlpha = 282 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 283 PropertyValuesHolder panelDriftY = 284 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 285 PropertyValuesHolder panelDriftX = 286 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 287 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 288 panelAlpha, panelDriftY, panelDriftX); 289 panelAlphaAndDrift.setDuration(revealDuration); 290 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 291 292 // Play the animation 293 layerViews.addView(revealView); 294 animation.play(panelAlphaAndDrift); 295 296 // Setup the animation for the content view 297 contentView.setVisibility(View.VISIBLE); 298 contentView.setAlpha(0f); 299 contentView.setTranslationY(revealViewToYDrift); 300 layerViews.addView(contentView); 301 302 // Create the individual animators 303 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 304 revealViewToYDrift, 0); 305 pageDrift.setDuration(revealDuration); 306 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 307 pageDrift.setStartDelay(itemsAlphaStagger); 308 animation.play(pageDrift); 309 310 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 311 itemsAlpha.setDuration(revealDuration); 312 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 313 itemsAlpha.setStartDelay(itemsAlphaStagger); 314 animation.play(itemsAlpha); 315 316 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 317 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 318 revealView, buttonView); 319 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 320 startRadius, revealRadius).createRevealAnimator(revealView); 321 reveal.setDuration(revealDuration); 322 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 323 if (listener != null) { 324 reveal.addListener(listener); 325 } 326 animation.play(reveal); 327 328 animation.addListener(new AnimatorListenerAdapter() { 329 @Override 330 public void onAnimationEnd(Animator animation) { 331 // Hide the reveal view 332 revealView.setVisibility(View.INVISIBLE); 333 334 // This can hold unnecessary references to views. 335 cleanupAnimation(); 336 pCb.onTransitionComplete(); 337 } 338 339 }); 340 341 toView.bringToFront(); 342 toView.setVisibility(View.VISIBLE); 343 344 animation.addListener(layerViews); 345 toView.post(new StartAnimRunnable(animation, toView)); 346 mCurrentAnimation = animation; 347 } else if (animType == PULLUP) { 348 // We are animating the content view alpha, so ensure we have a layer for it 349 layerViews.addView(contentView); 350 351 animation.addListener(new AnimatorListenerAdapter() { 352 @Override 353 public void onAnimationEnd(Animator animation) { 354 cleanupAnimation(); 355 pCb.onTransitionComplete(); 356 } 357 }); 358 boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); 359 360 Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); 361 mCurrentAnimation = animation; 362 mCurrentAnimation.addListener(layerViews); 363 if (shouldPost) { 364 toView.post(startAnimRunnable); 365 } else { 366 startAnimRunnable.run(); 367 } 368 } 369 } 370 371 /** 372 * Plays animations used by various transitions. 373 */ 374 private void playCommonTransitionAnimations( 375 Workspace.State toWorkspaceState, 376 boolean animated, boolean initialized, AnimatorSet animation, 377 AnimationLayerSet layerViews) { 378 // Create the workspace animation. 379 // NOTE: this call apparently also sets the state for the workspace if !animated 380 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 381 animated, layerViews); 382 383 if (animated && initialized) { 384 // Play the workspace animation 385 if (workspaceAnim != null) { 386 animation.play(workspaceAnim); 387 } 388 } 389 } 390 391 /** 392 * Starts an animation to the workspace from the apps view. 393 */ 394 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 395 final Workspace.State toWorkspaceState, final boolean animated, int type, 396 final Runnable onCompleteRunnable) { 397 final AllAppsContainerView appsView = mLauncher.getAppsView(); 398 // No alpha anim from all apps 399 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 400 @Override 401 float getMaterialRevealViewStartFinalRadius() { 402 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 403 return allAppsButtonSize / 2; 404 } 405 @Override 406 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 407 final View revealView, final View allAppsButtonView) { 408 return new AnimatorListenerAdapter() { 409 public void onAnimationStart(Animator animation) { 410 // We set the alpha instead of visibility to ensure that the focus does not 411 // get taken from the all apps view 412 allAppsButtonView.setVisibility(View.VISIBLE); 413 allAppsButtonView.setAlpha(0f); 414 } 415 public void onAnimationEnd(Animator animation) { 416 // Hide the reveal view 417 revealView.setVisibility(View.INVISIBLE); 418 419 // Show the all apps button, and focus it 420 allAppsButtonView.setAlpha(1f); 421 } 422 }; 423 } 424 @Override 425 void onTransitionComplete() { 426 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 427 appsView.reset(); 428 } 429 }; 430 // Only animate the search bar if animating to spring loaded mode from all apps 431 startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 432 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView, 433 animated, type, onCompleteRunnable, cb); 434 } 435 436 /** 437 * Starts an animation to the workspace from the widgets view. 438 */ 439 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 440 final Workspace.State toWorkspaceState, final boolean animated, 441 final Runnable onCompleteRunnable) { 442 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 443 PrivateTransitionCallbacks cb = 444 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 445 @Override 446 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 447 final View revealView, final View widgetsButtonView) { 448 return new AnimatorListenerAdapter() { 449 public void onAnimationEnd(Animator animation) { 450 // Hide the reveal view 451 revealView.setVisibility(View.INVISIBLE); 452 } 453 }; 454 } 455 @Override 456 void onTransitionComplete() { 457 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 458 } 459 }; 460 startAnimationToWorkspaceFromOverlay( 461 fromWorkspaceState, toWorkspaceState, 462 mLauncher.getWidgetsButton(), widgetsView, 463 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); 464 } 465 466 /** 467 * Starts an animation to the workspace from another workspace state, e.g. normal to overview. 468 */ 469 private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, 470 final Workspace.State toWorkspaceState, final boolean animated, 471 final Runnable onCompleteRunnable) { 472 final View fromWorkspace = mLauncher.getWorkspace(); 473 final AnimationLayerSet layerViews = new AnimationLayerSet(); 474 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 475 476 // Cancel the current animation 477 cancelAnimation(); 478 479 playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews); 480 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 481 482 if (animated) { 483 animation.addListener(new AnimatorListenerAdapter() { 484 @Override 485 public void onAnimationEnd(Animator animation) { 486 // Run any queued runnables 487 if (onCompleteRunnable != null) { 488 onCompleteRunnable.run(); 489 } 490 491 // This can hold unnecessary references to views. 492 cleanupAnimation(); 493 } 494 }); 495 animation.addListener(layerViews); 496 fromWorkspace.post(new StartAnimRunnable(animation, null)); 497 mCurrentAnimation = animation; 498 } else /* if (!animated) */ { 499 // Run any queued runnables 500 if (onCompleteRunnable != null) { 501 onCompleteRunnable.run(); 502 } 503 504 mCurrentAnimation = null; 505 } 506 } 507 508 /** 509 * Creates and starts a new animation to the workspace. 510 */ 511 private void startAnimationToWorkspaceFromOverlay( 512 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 513 final View buttonView, final BaseContainerView fromView, 514 final boolean animated, int animType, final Runnable onCompleteRunnable, 515 final PrivateTransitionCallbacks pCb) { 516 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 517 final Resources res = mLauncher.getResources(); 518 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 519 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 520 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 521 522 final View toView = mLauncher.getWorkspace(); 523 final View revealView = fromView.getRevealView(); 524 final View contentView = fromView.getContentView(); 525 526 final AnimationLayerSet layerViews = new AnimationLayerSet(); 527 528 // If for some reason our views aren't initialized, don't animate 529 boolean initialized = buttonView != null; 530 531 // Cancel the current animation 532 cancelAnimation(); 533 534 playCommonTransitionAnimations(toWorkspaceState, 535 animated, initialized, animation, layerViews); 536 if (!animated || !initialized) { 537 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 538 fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 539 mAllAppsController.finishPullDown(); 540 } 541 fromView.setVisibility(View.GONE); 542 pCb.onTransitionComplete(); 543 544 // Run any queued runnables 545 if (onCompleteRunnable != null) { 546 onCompleteRunnable.run(); 547 } 548 return; 549 } 550 if (animType == CIRCULAR_REVEAL) { 551 // hideAppsCustomizeHelper is called in some cases when it is already hidden 552 // don't perform all these no-op animations. In particularly, this was causing 553 // the all-apps button to pop in and out. 554 if (fromView.getVisibility() == View.VISIBLE) { 555 int width = revealView.getMeasuredWidth(); 556 int height = revealView.getMeasuredHeight(); 557 float revealRadius = (float) Math.hypot(width / 2, height / 2); 558 revealView.setVisibility(View.VISIBLE); 559 revealView.setAlpha(1f); 560 revealView.setTranslationY(0); 561 layerViews.addView(revealView); 562 563 // Calculate the final animation values 564 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); 565 final float revealViewToXDrift = buttonViewToPanelDelta[0]; 566 final float revealViewToYDrift = buttonViewToPanelDelta[1]; 567 568 // The vertical motion of the apps panel should be delayed by one frame 569 // from the conceal animation in order to give the right feel. We correspondingly 570 // shorten the duration so that the slide and conceal end at the same time. 571 TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0); 572 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 573 0, revealViewToYDrift); 574 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 575 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 576 panelDriftY.setInterpolator(decelerateInterpolator); 577 animation.play(panelDriftY); 578 579 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 580 0, revealViewToXDrift); 581 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 582 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 583 panelDriftX.setInterpolator(decelerateInterpolator); 584 animation.play(panelDriftX); 585 586 // Setup animation for the reveal panel alpha 587 if (pCb.materialRevealViewFinalAlpha != 1f) { 588 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 589 1f, pCb.materialRevealViewFinalAlpha); 590 panelAlpha.setDuration(revealDuration); 591 panelAlpha.setInterpolator(decelerateInterpolator); 592 animation.play(panelAlpha); 593 } 594 595 // Setup the animation for the content view 596 layerViews.addView(contentView); 597 598 // Create the individual animators 599 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 600 0, revealViewToYDrift); 601 contentView.setTranslationY(0); 602 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 603 pageDrift.setInterpolator(decelerateInterpolator); 604 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 605 animation.play(pageDrift); 606 607 contentView.setAlpha(1f); 608 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 609 itemsAlpha.setDuration(100); 610 itemsAlpha.setInterpolator(decelerateInterpolator); 611 animation.play(itemsAlpha); 612 613 // Invalidate the scrim throughout the animation to ensure the highlight 614 // cutout is correct throughout. 615 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); 616 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 617 @Override 618 public void onAnimationUpdate(ValueAnimator animation) { 619 mLauncher.getDragLayer().invalidateScrim(); 620 } 621 }); 622 animation.play(invalidateScrim); 623 624 // Animate the all apps button 625 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 626 AnimatorListenerAdapter listener = 627 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 628 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 629 revealRadius, finalRadius).createRevealAnimator(revealView); 630 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 631 reveal.setDuration(revealDuration); 632 reveal.setStartDelay(itemsAlphaStagger); 633 if (listener != null) { 634 reveal.addListener(listener); 635 } 636 animation.play(reveal); 637 } 638 639 animation.addListener(new AnimatorListenerAdapter() { 640 @Override 641 public void onAnimationEnd(Animator animation) { 642 fromView.setVisibility(View.GONE); 643 // Run any queued runnables 644 if (onCompleteRunnable != null) { 645 onCompleteRunnable.run(); 646 } 647 648 // Reset page transforms 649 if (contentView != null) { 650 contentView.setTranslationX(0); 651 contentView.setTranslationY(0); 652 contentView.setAlpha(1); 653 } 654 655 // This can hold unnecessary references to views. 656 cleanupAnimation(); 657 pCb.onTransitionComplete(); 658 } 659 }); 660 661 mCurrentAnimation = animation; 662 mCurrentAnimation.addListener(layerViews); 663 fromView.post(new StartAnimRunnable(animation, null)); 664 } else if (animType == PULLUP) { 665 // We are animating the content view alpha, so ensure we have a layer for it 666 layerViews.addView(contentView); 667 668 animation.addListener(new AnimatorListenerAdapter() { 669 boolean canceled = false; 670 @Override 671 public void onAnimationCancel(Animator animation) { 672 canceled = true; 673 } 674 675 @Override 676 public void onAnimationEnd(Animator animation) { 677 if (canceled) return; 678 // Run any queued runnables 679 if (onCompleteRunnable != null) { 680 onCompleteRunnable.run(); 681 } 682 683 cleanupAnimation(); 684 pCb.onTransitionComplete(); 685 } 686 687 }); 688 boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); 689 690 Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); 691 mCurrentAnimation = animation; 692 mCurrentAnimation.addListener(layerViews); 693 if (shouldPost) { 694 fromView.post(startAnimRunnable); 695 } else { 696 startAnimRunnable.run(); 697 } 698 } 699 return; 700 } 701 702 /** 703 * Cancels the current animation. 704 */ 705 private void cancelAnimation() { 706 if (mCurrentAnimation != null) { 707 mCurrentAnimation.setDuration(0); 708 mCurrentAnimation.cancel(); 709 mCurrentAnimation = null; 710 } 711 } 712 713 @Thunk void cleanupAnimation() { 714 mCurrentAnimation = null; 715 } 716 717 private class StartAnimRunnable implements Runnable { 718 719 private final AnimatorSet mAnim; 720 private final View mViewToFocus; 721 722 public StartAnimRunnable(AnimatorSet anim, View viewToFocus) { 723 mAnim = anim; 724 mViewToFocus = viewToFocus; 725 } 726 727 @Override 728 public void run() { 729 if (mCurrentAnimation != mAnim) { 730 return; 731 } 732 if (mViewToFocus != null) { 733 mViewToFocus.requestFocus(); 734 } 735 mAnim.start(); 736 } 737 } 738 } 739