LauncherStateTransitionAnimation.java revision 7ccc4625e38b8107cd1882272aec36967874a5cc
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.annotation.SuppressLint; 27import android.annotation.TargetApi; 28import android.content.res.Resources; 29import android.os.Build; 30import android.util.Log; 31import android.view.View; 32import android.view.animation.AccelerateInterpolator; 33import android.view.animation.DecelerateInterpolator; 34 35import com.android.launcher3.allapps.AllAppsContainerView; 36import com.android.launcher3.allapps.AllAppsTransitionController; 37import com.android.launcher3.config.FeatureFlags; 38import com.android.launcher3.util.CircleRevealOutlineProvider; 39import com.android.launcher3.util.Thunk; 40import com.android.launcher3.widget.WidgetsContainerView; 41 42import 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 */ 85public 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 mCurrentAnimation = 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 mCurrentAnimation = 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 AnimatorSet 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 toView.bringToFront(); 269 270 // Show the content view 271 contentView.setVisibility(View.VISIBLE); 272 273 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 274 dispatchOnLauncherTransitionStart(fromView, animated, false); 275 dispatchOnLauncherTransitionEnd(fromView, animated, false); 276 dispatchOnLauncherTransitionPrepare(toView, animated, false); 277 dispatchOnLauncherTransitionStart(toView, animated, false); 278 dispatchOnLauncherTransitionEnd(toView, animated, false); 279 pCb.onTransitionComplete(); 280 281 return null; 282 } 283 if (animType == CIRCULAR_REVEAL) { 284 // Setup the reveal view animation 285 final View revealView = toView.getRevealView(); 286 287 int width = revealView.getMeasuredWidth(); 288 int height = revealView.getMeasuredHeight(); 289 float revealRadius = (float) Math.hypot(width / 2, height / 2); 290 revealView.setVisibility(View.VISIBLE); 291 revealView.setAlpha(0f); 292 revealView.setTranslationY(0f); 293 revealView.setTranslationX(0f); 294 295 // Calculate the final animation values 296 final float revealViewToAlpha; 297 final float revealViewToXDrift; 298 final float revealViewToYDrift; 299 if (material) { 300 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace( 301 revealView, buttonView, null); 302 revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 303 revealViewToYDrift = buttonViewToPanelDelta[1]; 304 revealViewToXDrift = buttonViewToPanelDelta[0]; 305 } else { 306 revealViewToAlpha = 0f; 307 revealViewToYDrift = 2 * height / 3; 308 revealViewToXDrift = 0; 309 } 310 311 // Create the animators 312 PropertyValuesHolder panelAlpha = 313 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 314 PropertyValuesHolder panelDriftY = 315 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 316 PropertyValuesHolder panelDriftX = 317 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 318 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 319 panelAlpha, panelDriftY, panelDriftX); 320 panelAlphaAndDrift.setDuration(revealDuration); 321 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 322 323 // Play the animation 324 layerViews.put(revealView, BUILD_AND_SET_LAYER); 325 animation.play(panelAlphaAndDrift); 326 327 // Setup the animation for the content view 328 contentView.setVisibility(View.VISIBLE); 329 contentView.setAlpha(0f); 330 contentView.setTranslationY(revealViewToYDrift); 331 layerViews.put(contentView, BUILD_AND_SET_LAYER); 332 333 // Create the individual animators 334 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 335 revealViewToYDrift, 0); 336 pageDrift.setDuration(revealDuration); 337 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 338 pageDrift.setStartDelay(itemsAlphaStagger); 339 animation.play(pageDrift); 340 341 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 342 itemsAlpha.setDuration(revealDuration); 343 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 344 itemsAlpha.setStartDelay(itemsAlphaStagger); 345 animation.play(itemsAlpha); 346 347 if (material) { 348 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 349 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 350 revealView, buttonView); 351 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 352 startRadius, revealRadius).createRevealAnimator(revealView); 353 reveal.setDuration(revealDuration); 354 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 355 if (listener != null) { 356 reveal.addListener(listener); 357 } 358 animation.play(reveal); 359 } 360 361 animation.addListener(new AnimatorListenerAdapter() { 362 @Override 363 public void onAnimationEnd(Animator animation) { 364 dispatchOnLauncherTransitionEnd(fromView, animated, false); 365 dispatchOnLauncherTransitionEnd(toView, animated, false); 366 367 // Hide the reveal view 368 revealView.setVisibility(View.INVISIBLE); 369 370 // Disable all necessary layers 371 for (View v : layerViews.keySet()) { 372 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 373 v.setLayerType(View.LAYER_TYPE_NONE, null); 374 } 375 } 376 377 // This can hold unnecessary references to views. 378 cleanupAnimation(); 379 pCb.onTransitionComplete(); 380 } 381 382 }); 383 384 // Dispatch the prepare transition signal 385 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 386 dispatchOnLauncherTransitionPrepare(toView, animated, false); 387 388 final AnimatorSet stateAnimation = animation; 389 final Runnable startAnimRunnable = new Runnable() { 390 public void run() { 391 // Check that mCurrentAnimation hasn't changed while 392 // we waited for a layout/draw pass 393 if (mCurrentAnimation != stateAnimation) 394 return; 395 dispatchOnLauncherTransitionStart(fromView, animated, false); 396 dispatchOnLauncherTransitionStart(toView, animated, false); 397 398 // Enable all necessary layers 399 for (View v : layerViews.keySet()) { 400 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 401 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 402 } 403 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 404 v.buildLayer(); 405 } 406 } 407 408 // Focus the new view 409 toView.requestFocus(); 410 411 stateAnimation.start(); 412 } 413 }; 414 toView.bringToFront(); 415 toView.setVisibility(View.VISIBLE); 416 toView.post(startAnimRunnable); 417 418 return animation; 419 } else if (animType == PULLUP) { 420 // We are animating the content view alpha, so ensure we have a layer for it 421 layerViews.put(contentView, BUILD_AND_SET_LAYER); 422 423 animation.addListener(new AnimatorListenerAdapter() { 424 @Override 425 public void onAnimationEnd(Animator animation) { 426 dispatchOnLauncherTransitionEnd(fromView, animated, false); 427 dispatchOnLauncherTransitionEnd(toView, animated, false); 428 429 // Disable all necessary layers 430 for (View v : layerViews.keySet()) { 431 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 432 v.setLayerType(View.LAYER_TYPE_NONE, null); 433 } 434 } 435 436 cleanupAnimation(); 437 pCb.onTransitionComplete(); 438 } 439 }); 440 mAllAppsController.animateToAllApps(animation, revealDurationSlide); 441 442 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 443 dispatchOnLauncherTransitionPrepare(toView, animated, false); 444 445 final AnimatorSet stateAnimation = animation; 446 final Runnable startAnimRunnable = new Runnable() { 447 public void run() { 448 // Check that mCurrentAnimation hasn't changed while 449 // we waited for a layout/draw pass 450 if (mCurrentAnimation != stateAnimation) 451 return; 452 453 dispatchOnLauncherTransitionStart(fromView, animated, false); 454 dispatchOnLauncherTransitionStart(toView, animated, false); 455 456 // Enable all necessary layers 457 for (View v : layerViews.keySet()) { 458 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 459 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 460 } 461 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 462 v.buildLayer(); 463 } 464 } 465 466 toView.requestFocus(); 467 stateAnimation.start(); 468 } 469 }; 470 toView.post(startAnimRunnable); 471 return animation; 472 } 473 return null; 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 mCurrentAnimation = 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 mCurrentAnimation = 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 AnimatorSet 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 null; 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 fromView.post(startAnimRunnable); 889 890 return animation; 891 } else if (animType == PULLUP) { 892 // We are animating the content view alpha, so ensure we have a layer for it 893 layerViews.put(contentView, BUILD_AND_SET_LAYER); 894 895 animation.addListener(new AnimatorListenerAdapter() { 896 boolean canceled = false; 897 @Override 898 public void onAnimationCancel(Animator animation) { 899 canceled = true; 900 } 901 902 @Override 903 public void onAnimationEnd(Animator animation) { 904 if (canceled) return; 905 dispatchOnLauncherTransitionEnd(fromView, animated, true); 906 dispatchOnLauncherTransitionEnd(toView, animated, true); 907 // Run any queued runnables 908 if (onCompleteRunnable != null) { 909 onCompleteRunnable.run(); 910 } 911 912 // Disable all necessary layers 913 for (View v : layerViews.keySet()) { 914 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 915 v.setLayerType(View.LAYER_TYPE_NONE, null); 916 } 917 } 918 919 cleanupAnimation(); 920 pCb.onTransitionComplete(); 921 } 922 923 }); 924 mAllAppsController.animateToWorkspace(animation, revealDurationSlide); 925 926 // Dispatch the prepare transition signal 927 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 928 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 929 930 final AnimatorSet stateAnimation = animation; 931 final Runnable startAnimRunnable = new Runnable() { 932 public void run() { 933 // Check that mCurrentAnimation hasn't changed while 934 // we waited for a layout/draw pass 935 if (mCurrentAnimation != stateAnimation) 936 return; 937 938 dispatchOnLauncherTransitionStart(fromView, animated, false); 939 dispatchOnLauncherTransitionStart(toView, animated, false); 940 941 // Enable all necessary layers 942 for (View v : layerViews.keySet()) { 943 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 944 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 945 } 946 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 947 v.buildLayer(); 948 } 949 } 950 951 // Focus the new view 952 toView.requestFocus(); 953 stateAnimation.start(); 954 } 955 }; 956 fromView.post(startAnimRunnable); 957 return animation; 958 } 959 return null; 960 } 961 962 /** 963 * Dispatches the prepare-transition event to suitable views. 964 */ 965 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, 966 boolean multiplePagesVisible) { 967 if (v instanceof LauncherTransitionable) { 968 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 969 multiplePagesVisible); 970 } 971 } 972 973 /** 974 * Dispatches the start-transition event to suitable views. 975 */ 976 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 977 if (v instanceof LauncherTransitionable) { 978 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 979 toWorkspace); 980 } 981 982 // Update the workspace transition step as well 983 dispatchOnLauncherTransitionStep(v, 0f); 984 } 985 986 /** 987 * Dispatches the step-transition event to suitable views. 988 */ 989 void dispatchOnLauncherTransitionStep(View v, float t) { 990 if (v instanceof LauncherTransitionable) { 991 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 992 } 993 } 994 995 /** 996 * Dispatches the end-transition event to suitable views. 997 */ 998 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 999 if (v instanceof LauncherTransitionable) { 1000 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 1001 toWorkspace); 1002 } 1003 1004 // Update the workspace transition step as well 1005 dispatchOnLauncherTransitionStep(v, 1f); 1006 } 1007 1008 /** 1009 * Cancels the current animation. 1010 */ 1011 private void cancelAnimation() { 1012 if (mCurrentAnimation != null) { 1013 mCurrentAnimation.setDuration(0); 1014 mCurrentAnimation.cancel(); 1015 mCurrentAnimation = null; 1016 } 1017 } 1018 1019 @Thunk void cleanupAnimation() { 1020 mCurrentAnimation = null; 1021 } 1022} 1023