LauncherStateTransitionAnimation.java revision 08d8bb9cde705ef66561fb2dbed0af7af2632812
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 17package com.android.launcher3; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.PropertyValuesHolder; 24import android.animation.TimeInterpolator; 25import android.animation.ValueAnimator; 26import android.content.res.Resources; 27import android.util.Log; 28import android.view.View; 29import android.view.animation.AccelerateInterpolator; 30 31import com.android.launcher3.allapps.AllAppsContainerView; 32import com.android.launcher3.allapps.AllAppsTransitionController; 33import com.android.launcher3.anim.AnimationLayerSet; 34import com.android.launcher3.config.FeatureFlags; 35import com.android.launcher3.util.CircleRevealOutlineProvider; 36import com.android.launcher3.util.Thunk; 37import 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 */ 80public 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 revealView.setAlpha(1f); 339 revealView.setTranslationX(0f); 340 revealView.setTranslationY(0f); 341 contentView.setAlpha(1f); 342 contentView.setTranslationY(0f); 343 } 344 345 }); 346 347 toView.bringToFront(); 348 toView.setVisibility(View.VISIBLE); 349 350 animation.addListener(layerViews); 351 toView.post(new StartAnimRunnable(animation, toView)); 352 mCurrentAnimation = animation; 353 } else if (animType == PULLUP) { 354 // We are animating the content view alpha, so ensure we have a layer for it 355 layerViews.addView(contentView); 356 357 animation.addListener(new AnimatorListenerAdapter() { 358 @Override 359 public void onAnimationEnd(Animator animation) { 360 cleanupAnimation(); 361 pCb.onTransitionComplete(); 362 } 363 }); 364 boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); 365 366 Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); 367 mCurrentAnimation = animation; 368 mCurrentAnimation.addListener(layerViews); 369 if (shouldPost) { 370 toView.post(startAnimRunnable); 371 } else { 372 startAnimRunnable.run(); 373 } 374 } 375 } 376 377 /** 378 * Plays animations used by various transitions. 379 */ 380 private void playCommonTransitionAnimations( 381 Workspace.State toWorkspaceState, 382 boolean animated, boolean initialized, AnimatorSet animation, 383 AnimationLayerSet layerViews) { 384 // Create the workspace animation. 385 // NOTE: this call apparently also sets the state for the workspace if !animated 386 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 387 animated, layerViews); 388 389 if (animated && initialized) { 390 // Play the workspace animation 391 if (workspaceAnim != null) { 392 animation.play(workspaceAnim); 393 } 394 } 395 } 396 397 /** 398 * Starts an animation to the workspace from the apps view. 399 */ 400 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 401 final Workspace.State toWorkspaceState, final boolean animated, int type, 402 final Runnable onCompleteRunnable) { 403 final AllAppsContainerView appsView = mLauncher.getAppsView(); 404 // No alpha anim from all apps 405 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 406 @Override 407 float getMaterialRevealViewStartFinalRadius() { 408 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 409 return allAppsButtonSize / 2; 410 } 411 @Override 412 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 413 final View revealView, final View allAppsButtonView) { 414 return new AnimatorListenerAdapter() { 415 public void onAnimationStart(Animator animation) { 416 // We set the alpha instead of visibility to ensure that the focus does not 417 // get taken from the all apps view 418 allAppsButtonView.setVisibility(View.VISIBLE); 419 allAppsButtonView.setAlpha(0f); 420 } 421 public void onAnimationEnd(Animator animation) { 422 // Hide the reveal view 423 revealView.setVisibility(View.INVISIBLE); 424 425 // Show the all apps button, and focus it 426 allAppsButtonView.setAlpha(1f); 427 } 428 }; 429 } 430 @Override 431 void onTransitionComplete() { 432 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 433 appsView.reset(); 434 } 435 }; 436 // Only animate the search bar if animating to spring loaded mode from all apps 437 startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 438 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView, 439 animated, type, onCompleteRunnable, cb); 440 } 441 442 /** 443 * Starts an animation to the workspace from the widgets view. 444 */ 445 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 446 final Workspace.State toWorkspaceState, final boolean animated, 447 final Runnable onCompleteRunnable) { 448 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 449 PrivateTransitionCallbacks cb = 450 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 451 @Override 452 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 453 final View revealView, final View widgetsButtonView) { 454 return new AnimatorListenerAdapter() { 455 public void onAnimationEnd(Animator animation) { 456 // Hide the reveal view 457 revealView.setVisibility(View.INVISIBLE); 458 } 459 }; 460 } 461 @Override 462 void onTransitionComplete() { 463 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 464 } 465 }; 466 startAnimationToWorkspaceFromOverlay( 467 fromWorkspaceState, toWorkspaceState, 468 mLauncher.getWidgetsButton(), widgetsView, 469 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); 470 } 471 472 /** 473 * Starts an animation to the workspace from another workspace state, e.g. normal to overview. 474 */ 475 private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, 476 final Workspace.State toWorkspaceState, final boolean animated, 477 final Runnable onCompleteRunnable) { 478 final View fromWorkspace = mLauncher.getWorkspace(); 479 final AnimationLayerSet layerViews = new AnimationLayerSet(); 480 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 481 482 // Cancel the current animation 483 cancelAnimation(); 484 485 playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews); 486 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 487 488 if (animated) { 489 animation.addListener(new AnimatorListenerAdapter() { 490 @Override 491 public void onAnimationEnd(Animator animation) { 492 // Run any queued runnables 493 if (onCompleteRunnable != null) { 494 onCompleteRunnable.run(); 495 } 496 497 // This can hold unnecessary references to views. 498 cleanupAnimation(); 499 } 500 }); 501 animation.addListener(layerViews); 502 fromWorkspace.post(new StartAnimRunnable(animation, null)); 503 mCurrentAnimation = animation; 504 } else /* if (!animated) */ { 505 // Run any queued runnables 506 if (onCompleteRunnable != null) { 507 onCompleteRunnable.run(); 508 } 509 510 mCurrentAnimation = null; 511 } 512 } 513 514 /** 515 * Creates and starts a new animation to the workspace. 516 */ 517 private void startAnimationToWorkspaceFromOverlay( 518 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 519 final View buttonView, final BaseContainerView fromView, 520 final boolean animated, int animType, final Runnable onCompleteRunnable, 521 final PrivateTransitionCallbacks pCb) { 522 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 523 final Resources res = mLauncher.getResources(); 524 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 525 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 526 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 527 528 final View toView = mLauncher.getWorkspace(); 529 final View revealView = fromView.getRevealView(); 530 final View contentView = fromView.getContentView(); 531 532 final AnimationLayerSet layerViews = new AnimationLayerSet(); 533 534 // If for some reason our views aren't initialized, don't animate 535 boolean initialized = buttonView != null; 536 537 // Cancel the current animation 538 cancelAnimation(); 539 540 playCommonTransitionAnimations(toWorkspaceState, 541 animated, initialized, animation, layerViews); 542 if (!animated || !initialized) { 543 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 544 fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 545 mAllAppsController.finishPullDown(); 546 } 547 fromView.setVisibility(View.GONE); 548 pCb.onTransitionComplete(); 549 550 // Run any queued runnables 551 if (onCompleteRunnable != null) { 552 onCompleteRunnable.run(); 553 } 554 return; 555 } 556 if (animType == CIRCULAR_REVEAL) { 557 // hideAppsCustomizeHelper is called in some cases when it is already hidden 558 // don't perform all these no-op animations. In particularly, this was causing 559 // the all-apps button to pop in and out. 560 if (fromView.getVisibility() == View.VISIBLE) { 561 int width = revealView.getMeasuredWidth(); 562 int height = revealView.getMeasuredHeight(); 563 float revealRadius = (float) Math.hypot(width / 2, height / 2); 564 revealView.setVisibility(View.VISIBLE); 565 revealView.setAlpha(1f); 566 revealView.setTranslationY(0); 567 layerViews.addView(revealView); 568 569 // Calculate the final animation values 570 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); 571 final float revealViewToXDrift = buttonViewToPanelDelta[0]; 572 final float revealViewToYDrift = buttonViewToPanelDelta[1]; 573 574 // The vertical motion of the apps panel should be delayed by one frame 575 // from the conceal animation in order to give the right feel. We correspondingly 576 // shorten the duration so that the slide and conceal end at the same time. 577 TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0); 578 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 579 0, revealViewToYDrift); 580 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 581 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 582 panelDriftY.setInterpolator(decelerateInterpolator); 583 animation.play(panelDriftY); 584 585 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 586 0, revealViewToXDrift); 587 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 588 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 589 panelDriftX.setInterpolator(decelerateInterpolator); 590 animation.play(panelDriftX); 591 592 // Setup animation for the reveal panel alpha 593 if (pCb.materialRevealViewFinalAlpha != 1f) { 594 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 595 1f, pCb.materialRevealViewFinalAlpha); 596 panelAlpha.setDuration(revealDuration); 597 panelAlpha.setInterpolator(decelerateInterpolator); 598 animation.play(panelAlpha); 599 } 600 601 // Setup the animation for the content view 602 layerViews.addView(contentView); 603 604 // Create the individual animators 605 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 606 0, revealViewToYDrift); 607 contentView.setTranslationY(0); 608 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 609 pageDrift.setInterpolator(decelerateInterpolator); 610 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 611 animation.play(pageDrift); 612 613 contentView.setAlpha(1f); 614 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 615 itemsAlpha.setDuration(100); 616 itemsAlpha.setInterpolator(decelerateInterpolator); 617 animation.play(itemsAlpha); 618 619 // Invalidate the scrim throughout the animation to ensure the highlight 620 // cutout is correct throughout. 621 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); 622 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 623 @Override 624 public void onAnimationUpdate(ValueAnimator animation) { 625 mLauncher.getDragLayer().invalidateScrim(); 626 } 627 }); 628 animation.play(invalidateScrim); 629 630 // Animate the all apps button 631 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 632 AnimatorListenerAdapter listener = 633 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 634 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 635 revealRadius, finalRadius).createRevealAnimator(revealView); 636 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 637 reveal.setDuration(revealDuration); 638 reveal.setStartDelay(itemsAlphaStagger); 639 if (listener != null) { 640 reveal.addListener(listener); 641 } 642 animation.play(reveal); 643 } 644 645 animation.addListener(new AnimatorListenerAdapter() { 646 @Override 647 public void onAnimationEnd(Animator animation) { 648 fromView.setVisibility(View.GONE); 649 // Run any queued runnables 650 if (onCompleteRunnable != null) { 651 onCompleteRunnable.run(); 652 } 653 654 // Reset page transforms 655 if (contentView != null) { 656 contentView.setTranslationX(0); 657 contentView.setTranslationY(0); 658 contentView.setAlpha(1); 659 } 660 661 // This can hold unnecessary references to views. 662 cleanupAnimation(); 663 pCb.onTransitionComplete(); 664 } 665 }); 666 667 mCurrentAnimation = animation; 668 mCurrentAnimation.addListener(layerViews); 669 fromView.post(new StartAnimRunnable(animation, null)); 670 } else if (animType == PULLUP) { 671 // We are animating the content view alpha, so ensure we have a layer for it 672 layerViews.addView(contentView); 673 674 animation.addListener(new AnimatorListenerAdapter() { 675 boolean canceled = false; 676 @Override 677 public void onAnimationCancel(Animator animation) { 678 canceled = true; 679 } 680 681 @Override 682 public void onAnimationEnd(Animator animation) { 683 if (canceled) return; 684 // Run any queued runnables 685 if (onCompleteRunnable != null) { 686 onCompleteRunnable.run(); 687 } 688 689 cleanupAnimation(); 690 pCb.onTransitionComplete(); 691 } 692 693 }); 694 boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); 695 696 Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); 697 mCurrentAnimation = animation; 698 mCurrentAnimation.addListener(layerViews); 699 if (shouldPost) { 700 fromView.post(startAnimRunnable); 701 } else { 702 startAnimRunnable.run(); 703 } 704 } 705 return; 706 } 707 708 /** 709 * Cancels the current animation. 710 */ 711 private void cancelAnimation() { 712 if (mCurrentAnimation != null) { 713 mCurrentAnimation.setDuration(0); 714 mCurrentAnimation.cancel(); 715 mCurrentAnimation = null; 716 } 717 } 718 719 @Thunk void cleanupAnimation() { 720 mCurrentAnimation = null; 721 } 722 723 private class StartAnimRunnable implements Runnable { 724 725 private final AnimatorSet mAnim; 726 private final View mViewToFocus; 727 728 public StartAnimRunnable(AnimatorSet anim, View viewToFocus) { 729 mAnim = anim; 730 mViewToFocus = viewToFocus; 731 } 732 733 @Override 734 public void run() { 735 if (mCurrentAnimation != mAnim) { 736 return; 737 } 738 if (mViewToFocus != null) { 739 mViewToFocus.requestFocus(); 740 } 741 mAnim.start(); 742 } 743 } 744} 745