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.annotation.SuppressLint; 27 import android.annotation.TargetApi; 28 import android.content.res.Resources; 29 import android.os.Build; 30 import android.util.Log; 31 import android.view.View; 32 import android.view.animation.AccelerateInterpolator; 33 import android.view.animation.DecelerateInterpolator; 34 35 import com.android.launcher3.allapps.AllAppsContainerView; 36 import com.android.launcher3.allapps.AllAppsTransitionController; 37 import com.android.launcher3.config.FeatureFlags; 38 import com.android.launcher3.util.CircleRevealOutlineProvider; 39 import com.android.launcher3.util.Thunk; 40 import com.android.launcher3.widget.WidgetsContainerView; 41 42 import java.util.HashMap; 43 44 /** 45 * TODO: figure out what kind of tests we can write for this 46 * 47 * Things to test when changing the following class. 48 * - Home from workspace 49 * - from center screen 50 * - from other screens 51 * - Home from all apps 52 * - from center screen 53 * - from other screens 54 * - Back from all apps 55 * - from center screen 56 * - from other screens 57 * - Launch app from workspace and quit 58 * - with back 59 * - with home 60 * - Launch app from all apps and quit 61 * - with back 62 * - with home 63 * - Go to a screen that's not the default, then all 64 * apps, and launch and app, and go back 65 * - with back 66 * -with home 67 * - On workspace, long press power and go back 68 * - with back 69 * - with home 70 * - On all apps, long press power and go back 71 * - with back 72 * - with home 73 * - On workspace, power off 74 * - On all apps, power off 75 * - Launch an app and turn off the screen while in that app 76 * - Go back with home key 77 * - Go back with back key TODO: make this not go to workspace 78 * - From all apps 79 * - From workspace 80 * - Enter and exit car mode (becuase it causes an extra configuration changed) 81 * - From all apps 82 * - From the center workspace 83 * - From another workspace 84 */ 85 public class LauncherStateTransitionAnimation { 86 87 /** 88 * animation used for all apps and widget tray when 89 *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false} 90 */ 91 public static final int CIRCULAR_REVEAL = 0; 92 /** 93 * animation used for all apps and not widget tray when 94 *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true} 95 */ 96 public static final int PULLUP = 1; 97 98 private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f; 99 100 /** 101 * Private callbacks made during transition setup. 102 */ 103 private static class PrivateTransitionCallbacks { 104 private final float materialRevealViewFinalAlpha; 105 106 PrivateTransitionCallbacks(float revealAlpha) { 107 materialRevealViewFinalAlpha = revealAlpha; 108 } 109 110 float getMaterialRevealViewStartFinalRadius() { 111 return 0; 112 } 113 AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, 114 View buttonView) { 115 return null; 116 } 117 void onTransitionComplete() {} 118 } 119 120 public static final String TAG = "LSTAnimation"; 121 122 // Flags to determine how to set the layers on views before the transition animation 123 public static final int BUILD_LAYER = 0; 124 public static final int BUILD_AND_SET_LAYER = 1; 125 public static final int SINGLE_FRAME_DELAY = 16; 126 127 @Thunk Launcher mLauncher; 128 @Thunk AnimatorSet mCurrentAnimation; 129 AllAppsTransitionController mAllAppsController; 130 131 public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) { 132 mLauncher = l; 133 mAllAppsController = allAppsController; 134 } 135 136 /** 137 * Starts an animation to the apps view. 138 * 139 * @param startSearchAfterTransition Immediately starts app search after the transition to 140 * All Apps is completed. 141 */ 142 public void startAnimationToAllApps(final Workspace.State fromWorkspaceState, 143 final boolean animated, final boolean startSearchAfterTransition) { 144 final AllAppsContainerView toView = mLauncher.getAppsView(); 145 final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation(); 146 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 147 @Override 148 public float getMaterialRevealViewStartFinalRadius() { 149 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 150 return allAppsButtonSize / 2; 151 } 152 @Override 153 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 154 final View revealView, final View allAppsButtonView) { 155 return new AnimatorListenerAdapter() { 156 public void onAnimationStart(Animator animation) { 157 allAppsButtonView.setVisibility(View.INVISIBLE); 158 } 159 public void onAnimationEnd(Animator animation) { 160 allAppsButtonView.setVisibility(View.VISIBLE); 161 } 162 }; 163 } 164 @Override 165 void onTransitionComplete() { 166 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 167 if (startSearchAfterTransition) { 168 toView.startAppsSearch(); 169 } 170 } 171 }; 172 int animType = CIRCULAR_REVEAL; 173 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 174 animType = PULLUP; 175 } 176 // Only animate the search bar if animating from spring loaded mode back to all apps 177 startAnimationToOverlay(fromWorkspaceState, 178 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb); 179 } 180 181 /** 182 * Starts an animation to the widgets view. 183 */ 184 public void startAnimationToWidgets(final Workspace.State fromWorkspaceState, 185 final boolean animated) { 186 final WidgetsContainerView toView = mLauncher.getWidgetsView(); 187 final View buttonView = mLauncher.getWidgetsButton(); 188 startAnimationToOverlay(fromWorkspaceState, 189 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL, 190 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){ 191 @Override 192 void onTransitionComplete() { 193 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 194 } 195 }); 196 } 197 198 /** 199 * Starts an animation to the workspace from the current overlay view. 200 */ 201 public void startAnimationToWorkspace(final Launcher.State fromState, 202 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 203 final boolean animated, final Runnable onCompleteRunnable) { 204 if (toWorkspaceState != Workspace.State.NORMAL && 205 toWorkspaceState != Workspace.State.SPRING_LOADED && 206 toWorkspaceState != Workspace.State.OVERVIEW) { 207 Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); 208 } 209 210 if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED 211 || mAllAppsController.isTransitioning()) { 212 int animType = CIRCULAR_REVEAL; 213 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 214 animType = PULLUP; 215 } 216 startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, 217 animated, animType, onCompleteRunnable); 218 } else if (fromState == Launcher.State.WIDGETS || 219 fromState == Launcher.State.WIDGETS_SPRING_LOADED) { 220 startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, 221 animated, onCompleteRunnable); 222 } else { 223 startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, 224 animated, onCompleteRunnable); 225 } 226 } 227 228 /** 229 * Creates and starts a new animation to a particular overlay view. 230 */ 231 @SuppressLint("NewApi") 232 private void startAnimationToOverlay( 233 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 234 final View buttonView, final BaseContainerView toView, 235 final boolean animated, int animType, final PrivateTransitionCallbacks pCb) { 236 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 237 final Resources res = mLauncher.getResources(); 238 final boolean material = Utilities.ATLEAST_LOLLIPOP; 239 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 240 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 241 242 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 243 244 final View fromView = mLauncher.getWorkspace(); 245 246 final HashMap<View, Integer> layerViews = new HashMap<>(); 247 248 // If for some reason our views aren't initialized, don't animate 249 boolean initialized = buttonView != null; 250 251 // Cancel the current animation 252 cancelAnimation(); 253 254 final View contentView = toView.getContentView(); 255 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 256 animated, initialized, animation, layerViews); 257 if (!animated || !initialized) { 258 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 259 toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 260 mAllAppsController.finishPullUp(); 261 } 262 toView.setTranslationX(0.0f); 263 toView.setTranslationY(0.0f); 264 toView.setScaleX(1.0f); 265 toView.setScaleY(1.0f); 266 toView.setAlpha(1.0f); 267 toView.setVisibility(View.VISIBLE); 268 269 // Show the content view 270 contentView.setVisibility(View.VISIBLE); 271 272 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 273 dispatchOnLauncherTransitionStart(fromView, animated, false); 274 dispatchOnLauncherTransitionEnd(fromView, animated, false); 275 dispatchOnLauncherTransitionPrepare(toView, animated, false); 276 dispatchOnLauncherTransitionStart(toView, animated, false); 277 dispatchOnLauncherTransitionEnd(toView, animated, false); 278 pCb.onTransitionComplete(); 279 return; 280 } 281 if (animType == CIRCULAR_REVEAL) { 282 // Setup the reveal view animation 283 final View revealView = toView.getRevealView(); 284 285 int width = revealView.getMeasuredWidth(); 286 int height = revealView.getMeasuredHeight(); 287 float revealRadius = (float) Math.hypot(width / 2, height / 2); 288 revealView.setVisibility(View.VISIBLE); 289 revealView.setAlpha(0f); 290 revealView.setTranslationY(0f); 291 revealView.setTranslationX(0f); 292 293 // Calculate the final animation values 294 final float revealViewToAlpha; 295 final float revealViewToXDrift; 296 final float revealViewToYDrift; 297 if (material) { 298 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace( 299 revealView, buttonView, null); 300 revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 301 revealViewToYDrift = buttonViewToPanelDelta[1]; 302 revealViewToXDrift = buttonViewToPanelDelta[0]; 303 } else { 304 revealViewToAlpha = 0f; 305 revealViewToYDrift = 2 * height / 3; 306 revealViewToXDrift = 0; 307 } 308 309 // Create the animators 310 PropertyValuesHolder panelAlpha = 311 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 312 PropertyValuesHolder panelDriftY = 313 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 314 PropertyValuesHolder panelDriftX = 315 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 316 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 317 panelAlpha, panelDriftY, panelDriftX); 318 panelAlphaAndDrift.setDuration(revealDuration); 319 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 320 321 // Play the animation 322 layerViews.put(revealView, BUILD_AND_SET_LAYER); 323 animation.play(panelAlphaAndDrift); 324 325 // Setup the animation for the content view 326 contentView.setVisibility(View.VISIBLE); 327 contentView.setAlpha(0f); 328 contentView.setTranslationY(revealViewToYDrift); 329 layerViews.put(contentView, BUILD_AND_SET_LAYER); 330 331 // Create the individual animators 332 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 333 revealViewToYDrift, 0); 334 pageDrift.setDuration(revealDuration); 335 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 336 pageDrift.setStartDelay(itemsAlphaStagger); 337 animation.play(pageDrift); 338 339 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 340 itemsAlpha.setDuration(revealDuration); 341 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 342 itemsAlpha.setStartDelay(itemsAlphaStagger); 343 animation.play(itemsAlpha); 344 345 if (material) { 346 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 347 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 348 revealView, buttonView); 349 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 350 startRadius, revealRadius).createRevealAnimator(revealView); 351 reveal.setDuration(revealDuration); 352 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 353 if (listener != null) { 354 reveal.addListener(listener); 355 } 356 animation.play(reveal); 357 } 358 359 animation.addListener(new AnimatorListenerAdapter() { 360 @Override 361 public void onAnimationEnd(Animator animation) { 362 dispatchOnLauncherTransitionEnd(fromView, animated, false); 363 dispatchOnLauncherTransitionEnd(toView, animated, false); 364 365 // Hide the reveal view 366 revealView.setVisibility(View.INVISIBLE); 367 368 // Disable all necessary layers 369 for (View v : layerViews.keySet()) { 370 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 371 v.setLayerType(View.LAYER_TYPE_NONE, null); 372 } 373 } 374 375 // This can hold unnecessary references to views. 376 cleanupAnimation(); 377 pCb.onTransitionComplete(); 378 } 379 380 }); 381 382 // Dispatch the prepare transition signal 383 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 384 dispatchOnLauncherTransitionPrepare(toView, animated, false); 385 386 final AnimatorSet stateAnimation = animation; 387 final Runnable startAnimRunnable = new Runnable() { 388 public void run() { 389 // Check that mCurrentAnimation hasn't changed while 390 // we waited for a layout/draw pass 391 if (mCurrentAnimation != stateAnimation) 392 return; 393 dispatchOnLauncherTransitionStart(fromView, animated, false); 394 dispatchOnLauncherTransitionStart(toView, animated, false); 395 396 // Enable all necessary layers 397 for (View v : layerViews.keySet()) { 398 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 399 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 400 } 401 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 402 v.buildLayer(); 403 } 404 } 405 406 // Focus the new view 407 toView.requestFocus(); 408 409 stateAnimation.start(); 410 } 411 }; 412 toView.bringToFront(); 413 toView.setVisibility(View.VISIBLE); 414 toView.post(startAnimRunnable); 415 mCurrentAnimation = animation; 416 } else if (animType == PULLUP) { 417 // We are animating the content view alpha, so ensure we have a layer for it 418 layerViews.put(contentView, BUILD_AND_SET_LAYER); 419 420 animation.addListener(new AnimatorListenerAdapter() { 421 @Override 422 public void onAnimationEnd(Animator animation) { 423 dispatchOnLauncherTransitionEnd(fromView, animated, false); 424 dispatchOnLauncherTransitionEnd(toView, animated, false); 425 426 // Disable all necessary layers 427 for (View v : layerViews.keySet()) { 428 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 429 v.setLayerType(View.LAYER_TYPE_NONE, null); 430 } 431 } 432 433 cleanupAnimation(); 434 pCb.onTransitionComplete(); 435 } 436 }); 437 boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); 438 439 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 440 dispatchOnLauncherTransitionPrepare(toView, animated, false); 441 442 final AnimatorSet stateAnimation = animation; 443 final Runnable startAnimRunnable = new Runnable() { 444 public void run() { 445 // Check that mCurrentAnimation hasn't changed while 446 // we waited for a layout/draw pass 447 if (mCurrentAnimation != stateAnimation) 448 return; 449 450 dispatchOnLauncherTransitionStart(fromView, animated, false); 451 dispatchOnLauncherTransitionStart(toView, animated, false); 452 453 // Enable all necessary layers 454 for (View v : layerViews.keySet()) { 455 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 456 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 457 } 458 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 459 v.buildLayer(); 460 } 461 } 462 463 toView.requestFocus(); 464 stateAnimation.start(); 465 } 466 }; 467 mCurrentAnimation = animation; 468 if (shouldPost) { 469 toView.post(startAnimRunnable); 470 } else { 471 startAnimRunnable.run(); 472 } 473 } 474 } 475 476 /** 477 * Plays animations used by various transitions. 478 */ 479 private void playCommonTransitionAnimations( 480 Workspace.State toWorkspaceState, View fromView, View toView, 481 boolean animated, boolean initialized, AnimatorSet animation, 482 HashMap<View, Integer> layerViews) { 483 // Create the workspace animation. 484 // NOTE: this call apparently also sets the state for the workspace if !animated 485 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 486 animated, layerViews); 487 488 if (animated && initialized) { 489 // Play the workspace animation 490 if (workspaceAnim != null) { 491 animation.play(workspaceAnim); 492 } 493 // Dispatch onLauncherTransitionStep() as the animation interpolates. 494 animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView)); 495 } 496 } 497 498 /** 499 * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on 500 * {@param fromView} and {@param toView} as the animation interpolates. 501 * 502 * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener. 503 */ 504 private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) { 505 ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1); 506 updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 507 @Override 508 public void onAnimationUpdate(ValueAnimator animation) { 509 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction()); 510 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction()); 511 } 512 }); 513 return updateAnimator; 514 } 515 516 /** 517 * Starts an animation to the workspace from the apps view. 518 */ 519 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 520 final Workspace.State toWorkspaceState, final boolean animated, int type, 521 final Runnable onCompleteRunnable) { 522 AllAppsContainerView appsView = mLauncher.getAppsView(); 523 // No alpha anim from all apps 524 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 525 @Override 526 float getMaterialRevealViewStartFinalRadius() { 527 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 528 return allAppsButtonSize / 2; 529 } 530 @Override 531 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 532 final View revealView, final View allAppsButtonView) { 533 return new AnimatorListenerAdapter() { 534 public void onAnimationStart(Animator animation) { 535 // We set the alpha instead of visibility to ensure that the focus does not 536 // get taken from the all apps view 537 allAppsButtonView.setVisibility(View.VISIBLE); 538 allAppsButtonView.setAlpha(0f); 539 } 540 public void onAnimationEnd(Animator animation) { 541 // Hide the reveal view 542 revealView.setVisibility(View.INVISIBLE); 543 544 // Show the all apps button, and focus it 545 allAppsButtonView.setAlpha(1f); 546 } 547 }; 548 } 549 @Override 550 void onTransitionComplete() { 551 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 552 } 553 }; 554 // Only animate the search bar if animating to spring loaded mode from all apps 555 startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 556 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView, 557 animated, type, onCompleteRunnable, cb); 558 } 559 560 /** 561 * Starts an animation to the workspace from the widgets view. 562 */ 563 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 564 final Workspace.State toWorkspaceState, final boolean animated, 565 final Runnable onCompleteRunnable) { 566 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 567 PrivateTransitionCallbacks cb = 568 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 569 @Override 570 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 571 final View revealView, final View widgetsButtonView) { 572 return new AnimatorListenerAdapter() { 573 public void onAnimationEnd(Animator animation) { 574 // Hide the reveal view 575 revealView.setVisibility(View.INVISIBLE); 576 } 577 }; 578 } 579 @Override 580 void onTransitionComplete() { 581 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 582 } 583 }; 584 startAnimationToWorkspaceFromOverlay( 585 fromWorkspaceState, toWorkspaceState, 586 mLauncher.getWidgetsButton(), widgetsView, 587 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); 588 } 589 590 /** 591 * Starts an animation to the workspace from another workspace state, e.g. normal to overview. 592 */ 593 private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, 594 final Workspace.State toWorkspaceState, final boolean animated, 595 final Runnable onCompleteRunnable) { 596 final View fromWorkspace = mLauncher.getWorkspace(); 597 final HashMap<View, Integer> layerViews = new HashMap<>(); 598 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 599 final int revealDuration = mLauncher.getResources() 600 .getInteger(R.integer.config_overlayRevealTime); 601 602 // Cancel the current animation 603 cancelAnimation(); 604 605 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 606 607 playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null, 608 animated, animated, animation, layerViews); 609 610 if (animated) { 611 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 612 613 final AnimatorSet stateAnimation = animation; 614 final Runnable startAnimRunnable = new Runnable() { 615 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 616 public void run() { 617 // Check that mCurrentAnimation hasn't changed while 618 // we waited for a layout/draw pass 619 if (mCurrentAnimation != stateAnimation) 620 return; 621 622 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 623 624 // Enable all necessary layers 625 for (View v : layerViews.keySet()) { 626 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 627 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 628 } 629 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 630 v.buildLayer(); 631 } 632 } 633 stateAnimation.start(); 634 } 635 }; 636 animation.addListener(new AnimatorListenerAdapter() { 637 @Override 638 public void onAnimationEnd(Animator animation) { 639 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 640 641 // Run any queued runnables 642 if (onCompleteRunnable != null) { 643 onCompleteRunnable.run(); 644 } 645 646 // Disable all necessary layers 647 for (View v : layerViews.keySet()) { 648 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 649 v.setLayerType(View.LAYER_TYPE_NONE, null); 650 } 651 } 652 653 // This can hold unnecessary references to views. 654 cleanupAnimation(); 655 } 656 }); 657 fromWorkspace.post(startAnimRunnable); 658 mCurrentAnimation = animation; 659 } else /* if (!animated) */ { 660 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 661 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 662 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 663 664 // Run any queued runnables 665 if (onCompleteRunnable != null) { 666 onCompleteRunnable.run(); 667 } 668 669 mCurrentAnimation = null; 670 } 671 } 672 673 /** 674 * Creates and starts a new animation to the workspace. 675 */ 676 private void startAnimationToWorkspaceFromOverlay( 677 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 678 final View buttonView, final BaseContainerView fromView, 679 final boolean animated, int animType, final Runnable onCompleteRunnable, 680 final PrivateTransitionCallbacks pCb) { 681 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 682 final Resources res = mLauncher.getResources(); 683 final boolean material = Utilities.ATLEAST_LOLLIPOP; 684 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 685 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 686 final int itemsAlphaStagger = 687 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 688 689 final View toView = mLauncher.getWorkspace(); 690 final View revealView = fromView.getRevealView(); 691 final View contentView = fromView.getContentView(); 692 693 final HashMap<View, Integer> layerViews = new HashMap<>(); 694 695 // If for some reason our views aren't initialized, don't animate 696 boolean initialized = buttonView != null; 697 698 // Cancel the current animation 699 cancelAnimation(); 700 701 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 702 703 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 704 animated, initialized, animation, layerViews); 705 if (!animated || !initialized) { 706 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 707 fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 708 mAllAppsController.finishPullDown(); 709 } 710 fromView.setVisibility(View.GONE); 711 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 712 dispatchOnLauncherTransitionStart(fromView, animated, true); 713 dispatchOnLauncherTransitionEnd(fromView, animated, true); 714 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 715 dispatchOnLauncherTransitionStart(toView, animated, true); 716 dispatchOnLauncherTransitionEnd(toView, animated, true); 717 pCb.onTransitionComplete(); 718 719 // Run any queued runnables 720 if (onCompleteRunnable != null) { 721 onCompleteRunnable.run(); 722 } 723 return; 724 } 725 if (animType == CIRCULAR_REVEAL) { 726 // hideAppsCustomizeHelper is called in some cases when it is already hidden 727 // don't perform all these no-op animations. In particularly, this was causing 728 // the all-apps button to pop in and out. 729 if (fromView.getVisibility() == View.VISIBLE) { 730 int width = revealView.getMeasuredWidth(); 731 int height = revealView.getMeasuredHeight(); 732 float revealRadius = (float) Math.hypot(width / 2, height / 2); 733 revealView.setVisibility(View.VISIBLE); 734 revealView.setAlpha(1f); 735 revealView.setTranslationY(0); 736 layerViews.put(revealView, BUILD_AND_SET_LAYER); 737 738 // Calculate the final animation values 739 final float revealViewToXDrift; 740 final float revealViewToYDrift; 741 if (material) { 742 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 743 buttonView, null); 744 revealViewToYDrift = buttonViewToPanelDelta[1]; 745 revealViewToXDrift = buttonViewToPanelDelta[0]; 746 } else { 747 revealViewToYDrift = 2 * height / 3; 748 revealViewToXDrift = 0; 749 } 750 751 // The vertical motion of the apps panel should be delayed by one frame 752 // from the conceal animation in order to give the right feel. We correspondingly 753 // shorten the duration so that the slide and conceal end at the same time. 754 TimeInterpolator decelerateInterpolator = material ? 755 new LogDecelerateInterpolator(100, 0) : 756 new DecelerateInterpolator(1f); 757 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 758 0, revealViewToYDrift); 759 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 760 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 761 panelDriftY.setInterpolator(decelerateInterpolator); 762 animation.play(panelDriftY); 763 764 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 765 0, revealViewToXDrift); 766 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 767 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 768 panelDriftX.setInterpolator(decelerateInterpolator); 769 animation.play(panelDriftX); 770 771 // Setup animation for the reveal panel alpha 772 final float revealViewToAlpha = !material ? 0f : 773 pCb.materialRevealViewFinalAlpha; 774 if (revealViewToAlpha != 1f) { 775 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 776 1f, revealViewToAlpha); 777 panelAlpha.setDuration(material ? revealDuration : 150); 778 panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 779 panelAlpha.setInterpolator(decelerateInterpolator); 780 animation.play(panelAlpha); 781 } 782 783 // Setup the animation for the content view 784 layerViews.put(contentView, BUILD_AND_SET_LAYER); 785 786 // Create the individual animators 787 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 788 0, revealViewToYDrift); 789 contentView.setTranslationY(0); 790 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 791 pageDrift.setInterpolator(decelerateInterpolator); 792 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 793 animation.play(pageDrift); 794 795 contentView.setAlpha(1f); 796 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 797 itemsAlpha.setDuration(100); 798 itemsAlpha.setInterpolator(decelerateInterpolator); 799 animation.play(itemsAlpha); 800 801 // Invalidate the scrim throughout the animation to ensure the highlight 802 // cutout is correct throughout. 803 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); 804 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 805 @Override 806 public void onAnimationUpdate(ValueAnimator animation) { 807 mLauncher.getDragLayer().invalidateScrim(); 808 } 809 }); 810 animation.play(invalidateScrim); 811 812 if (material) { 813 // Animate the all apps button 814 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 815 AnimatorListenerAdapter listener = 816 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 817 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 818 revealRadius, finalRadius).createRevealAnimator(revealView); 819 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 820 reveal.setDuration(revealDuration); 821 reveal.setStartDelay(itemsAlphaStagger); 822 if (listener != null) { 823 reveal.addListener(listener); 824 } 825 animation.play(reveal); 826 } 827 } 828 829 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 830 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 831 832 animation.addListener(new AnimatorListenerAdapter() { 833 @Override 834 public void onAnimationEnd(Animator animation) { 835 fromView.setVisibility(View.GONE); 836 dispatchOnLauncherTransitionEnd(fromView, animated, true); 837 dispatchOnLauncherTransitionEnd(toView, animated, true); 838 839 // Run any queued runnables 840 if (onCompleteRunnable != null) { 841 onCompleteRunnable.run(); 842 } 843 844 // Disable all necessary layers 845 for (View v : layerViews.keySet()) { 846 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 847 v.setLayerType(View.LAYER_TYPE_NONE, null); 848 } 849 } 850 851 // Reset page transforms 852 if (contentView != null) { 853 contentView.setTranslationX(0); 854 contentView.setTranslationY(0); 855 contentView.setAlpha(1); 856 } 857 858 // This can hold unnecessary references to views. 859 cleanupAnimation(); 860 pCb.onTransitionComplete(); 861 } 862 }); 863 864 final AnimatorSet stateAnimation = animation; 865 final Runnable startAnimRunnable = new Runnable() { 866 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 867 public void run() { 868 // Check that mCurrentAnimation hasn't changed while 869 // we waited for a layout/draw pass 870 if (mCurrentAnimation != stateAnimation) 871 return; 872 873 dispatchOnLauncherTransitionStart(fromView, animated, false); 874 dispatchOnLauncherTransitionStart(toView, animated, false); 875 876 // Enable all necessary layers 877 for (View v : layerViews.keySet()) { 878 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 879 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 880 } 881 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 882 v.buildLayer(); 883 } 884 } 885 stateAnimation.start(); 886 } 887 }; 888 mCurrentAnimation = animation; 889 fromView.post(startAnimRunnable); 890 } else if (animType == PULLUP) { 891 // We are animating the content view alpha, so ensure we have a layer for it 892 layerViews.put(contentView, BUILD_AND_SET_LAYER); 893 894 animation.addListener(new AnimatorListenerAdapter() { 895 boolean canceled = false; 896 @Override 897 public void onAnimationCancel(Animator animation) { 898 canceled = true; 899 } 900 901 @Override 902 public void onAnimationEnd(Animator animation) { 903 if (canceled) return; 904 dispatchOnLauncherTransitionEnd(fromView, animated, true); 905 dispatchOnLauncherTransitionEnd(toView, animated, true); 906 // Run any queued runnables 907 if (onCompleteRunnable != null) { 908 onCompleteRunnable.run(); 909 } 910 911 // Disable all necessary layers 912 for (View v : layerViews.keySet()) { 913 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 914 v.setLayerType(View.LAYER_TYPE_NONE, null); 915 } 916 } 917 918 cleanupAnimation(); 919 pCb.onTransitionComplete(); 920 } 921 922 }); 923 boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); 924 925 // Dispatch the prepare transition signal 926 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 927 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 928 929 final AnimatorSet stateAnimation = animation; 930 final Runnable startAnimRunnable = new Runnable() { 931 public void run() { 932 // Check that mCurrentAnimation hasn't changed while 933 // we waited for a layout/draw pass 934 if (mCurrentAnimation != stateAnimation) 935 return; 936 937 dispatchOnLauncherTransitionStart(fromView, animated, false); 938 dispatchOnLauncherTransitionStart(toView, animated, false); 939 940 // Enable all necessary layers 941 for (View v : layerViews.keySet()) { 942 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 943 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 944 } 945 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 946 v.buildLayer(); 947 } 948 } 949 950 // Focus the new view 951 toView.requestFocus(); 952 stateAnimation.start(); 953 } 954 }; 955 mCurrentAnimation = animation; 956 if (shouldPost) { 957 fromView.post(startAnimRunnable); 958 } else { 959 startAnimRunnable.run(); 960 } 961 } 962 return; 963 } 964 965 /** 966 * Dispatches the prepare-transition event to suitable views. 967 */ 968 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, 969 boolean multiplePagesVisible) { 970 if (v instanceof LauncherTransitionable) { 971 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 972 multiplePagesVisible); 973 } 974 } 975 976 /** 977 * Dispatches the start-transition event to suitable views. 978 */ 979 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 980 if (v instanceof LauncherTransitionable) { 981 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 982 toWorkspace); 983 } 984 985 // Update the workspace transition step as well 986 dispatchOnLauncherTransitionStep(v, 0f); 987 } 988 989 /** 990 * Dispatches the step-transition event to suitable views. 991 */ 992 void dispatchOnLauncherTransitionStep(View v, float t) { 993 if (v instanceof LauncherTransitionable) { 994 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 995 } 996 } 997 998 /** 999 * Dispatches the end-transition event to suitable views. 1000 */ 1001 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 1002 if (v instanceof LauncherTransitionable) { 1003 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 1004 toWorkspace); 1005 } 1006 1007 // Update the workspace transition step as well 1008 dispatchOnLauncherTransitionStep(v, 1f); 1009 } 1010 1011 /** 1012 * Cancels the current animation. 1013 */ 1014 private void cancelAnimation() { 1015 if (mCurrentAnimation != null) { 1016 mCurrentAnimation.setDuration(0); 1017 mCurrentAnimation.cancel(); 1018 mCurrentAnimation = null; 1019 } 1020 } 1021 1022 @Thunk void cleanupAnimation() { 1023 mCurrentAnimation = null; 1024 } 1025 } 1026