LauncherStateTransitionAnimation.java revision 191e9d1b297f3a5dd2953f00c9cf9eac364fcf69
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 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 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 return; 281 } 282 if (animType == CIRCULAR_REVEAL) { 283 // Setup the reveal view animation 284 final View revealView = toView.getRevealView(); 285 286 int width = revealView.getMeasuredWidth(); 287 int height = revealView.getMeasuredHeight(); 288 float revealRadius = (float) Math.hypot(width / 2, height / 2); 289 revealView.setVisibility(View.VISIBLE); 290 revealView.setAlpha(0f); 291 revealView.setTranslationY(0f); 292 revealView.setTranslationX(0f); 293 294 // Calculate the final animation values 295 final float revealViewToAlpha; 296 final float revealViewToXDrift; 297 final float revealViewToYDrift; 298 if (material) { 299 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace( 300 revealView, buttonView, null); 301 revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 302 revealViewToYDrift = buttonViewToPanelDelta[1]; 303 revealViewToXDrift = buttonViewToPanelDelta[0]; 304 } else { 305 revealViewToAlpha = 0f; 306 revealViewToYDrift = 2 * height / 3; 307 revealViewToXDrift = 0; 308 } 309 310 // Create the animators 311 PropertyValuesHolder panelAlpha = 312 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 313 PropertyValuesHolder panelDriftY = 314 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 315 PropertyValuesHolder panelDriftX = 316 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 317 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 318 panelAlpha, panelDriftY, panelDriftX); 319 panelAlphaAndDrift.setDuration(revealDuration); 320 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 321 322 // Play the animation 323 layerViews.put(revealView, BUILD_AND_SET_LAYER); 324 animation.play(panelAlphaAndDrift); 325 326 // Setup the animation for the content view 327 contentView.setVisibility(View.VISIBLE); 328 contentView.setAlpha(0f); 329 contentView.setTranslationY(revealViewToYDrift); 330 layerViews.put(contentView, BUILD_AND_SET_LAYER); 331 332 // Create the individual animators 333 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 334 revealViewToYDrift, 0); 335 pageDrift.setDuration(revealDuration); 336 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 337 pageDrift.setStartDelay(itemsAlphaStagger); 338 animation.play(pageDrift); 339 340 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 341 itemsAlpha.setDuration(revealDuration); 342 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 343 itemsAlpha.setStartDelay(itemsAlphaStagger); 344 animation.play(itemsAlpha); 345 346 if (material) { 347 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 348 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 349 revealView, buttonView); 350 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 351 startRadius, revealRadius).createRevealAnimator(revealView); 352 reveal.setDuration(revealDuration); 353 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 354 if (listener != null) { 355 reveal.addListener(listener); 356 } 357 animation.play(reveal); 358 } 359 360 animation.addListener(new AnimatorListenerAdapter() { 361 @Override 362 public void onAnimationEnd(Animator animation) { 363 dispatchOnLauncherTransitionEnd(fromView, animated, false); 364 dispatchOnLauncherTransitionEnd(toView, animated, false); 365 366 // Hide the reveal view 367 revealView.setVisibility(View.INVISIBLE); 368 369 // Disable all necessary layers 370 for (View v : layerViews.keySet()) { 371 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 372 v.setLayerType(View.LAYER_TYPE_NONE, null); 373 } 374 } 375 376 // This can hold unnecessary references to views. 377 cleanupAnimation(); 378 pCb.onTransitionComplete(); 379 } 380 381 }); 382 383 // Dispatch the prepare transition signal 384 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 385 dispatchOnLauncherTransitionPrepare(toView, animated, false); 386 387 final AnimatorSet stateAnimation = animation; 388 final Runnable startAnimRunnable = new Runnable() { 389 public void run() { 390 // Check that mCurrentAnimation hasn't changed while 391 // we waited for a layout/draw pass 392 if (mCurrentAnimation != stateAnimation) 393 return; 394 dispatchOnLauncherTransitionStart(fromView, animated, false); 395 dispatchOnLauncherTransitionStart(toView, animated, false); 396 397 // Enable all necessary layers 398 for (View v : layerViews.keySet()) { 399 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 400 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 401 } 402 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 403 v.buildLayer(); 404 } 405 } 406 407 // Focus the new view 408 toView.requestFocus(); 409 410 stateAnimation.start(); 411 } 412 }; 413 toView.bringToFront(); 414 toView.setVisibility(View.VISIBLE); 415 toView.post(startAnimRunnable); 416 mCurrentAnimation = animation; 417 } else if (animType == PULLUP) { 418 // We are animating the content view alpha, so ensure we have a layer for it 419 layerViews.put(contentView, BUILD_AND_SET_LAYER); 420 421 animation.addListener(new AnimatorListenerAdapter() { 422 @Override 423 public void onAnimationEnd(Animator animation) { 424 dispatchOnLauncherTransitionEnd(fromView, animated, false); 425 dispatchOnLauncherTransitionEnd(toView, animated, false); 426 427 // Disable all necessary layers 428 for (View v : layerViews.keySet()) { 429 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 430 v.setLayerType(View.LAYER_TYPE_NONE, null); 431 } 432 } 433 434 cleanupAnimation(); 435 pCb.onTransitionComplete(); 436 } 437 }); 438 boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); 439 440 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 441 dispatchOnLauncherTransitionPrepare(toView, animated, false); 442 443 final AnimatorSet stateAnimation = animation; 444 final Runnable startAnimRunnable = new Runnable() { 445 public void run() { 446 // Check that mCurrentAnimation hasn't changed while 447 // we waited for a layout/draw pass 448 if (mCurrentAnimation != stateAnimation) 449 return; 450 451 dispatchOnLauncherTransitionStart(fromView, animated, false); 452 dispatchOnLauncherTransitionStart(toView, animated, false); 453 454 // Enable all necessary layers 455 for (View v : layerViews.keySet()) { 456 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 457 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 458 } 459 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 460 v.buildLayer(); 461 } 462 } 463 464 toView.requestFocus(); 465 stateAnimation.start(); 466 } 467 }; 468 mCurrentAnimation = animation; 469 if (shouldPost) { 470 toView.post(startAnimRunnable); 471 } else { 472 startAnimRunnable.run(); 473 } 474 } 475 } 476 477 /** 478 * Plays animations used by various transitions. 479 */ 480 private void playCommonTransitionAnimations( 481 Workspace.State toWorkspaceState, View fromView, View toView, 482 boolean animated, boolean initialized, AnimatorSet animation, 483 HashMap<View, Integer> layerViews) { 484 // Create the workspace animation. 485 // NOTE: this call apparently also sets the state for the workspace if !animated 486 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 487 animated, layerViews); 488 489 if (animated && initialized) { 490 // Play the workspace animation 491 if (workspaceAnim != null) { 492 animation.play(workspaceAnim); 493 } 494 // Dispatch onLauncherTransitionStep() as the animation interpolates. 495 animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView)); 496 } 497 } 498 499 /** 500 * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on 501 * {@param fromView} and {@param toView} as the animation interpolates. 502 * 503 * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener. 504 */ 505 private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) { 506 ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1); 507 updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 508 @Override 509 public void onAnimationUpdate(ValueAnimator animation) { 510 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction()); 511 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction()); 512 } 513 }); 514 return updateAnimator; 515 } 516 517 /** 518 * Starts an animation to the workspace from the apps view. 519 */ 520 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 521 final Workspace.State toWorkspaceState, final boolean animated, int type, 522 final Runnable onCompleteRunnable) { 523 AllAppsContainerView appsView = mLauncher.getAppsView(); 524 // No alpha anim from all apps 525 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 526 @Override 527 float getMaterialRevealViewStartFinalRadius() { 528 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 529 return allAppsButtonSize / 2; 530 } 531 @Override 532 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 533 final View revealView, final View allAppsButtonView) { 534 return new AnimatorListenerAdapter() { 535 public void onAnimationStart(Animator animation) { 536 // We set the alpha instead of visibility to ensure that the focus does not 537 // get taken from the all apps view 538 allAppsButtonView.setVisibility(View.VISIBLE); 539 allAppsButtonView.setAlpha(0f); 540 } 541 public void onAnimationEnd(Animator animation) { 542 // Hide the reveal view 543 revealView.setVisibility(View.INVISIBLE); 544 545 // Show the all apps button, and focus it 546 allAppsButtonView.setAlpha(1f); 547 } 548 }; 549 } 550 @Override 551 void onTransitionComplete() { 552 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 553 } 554 }; 555 // Only animate the search bar if animating to spring loaded mode from all apps 556 startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 557 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView, 558 animated, type, onCompleteRunnable, cb); 559 } 560 561 /** 562 * Starts an animation to the workspace from the widgets view. 563 */ 564 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 565 final Workspace.State toWorkspaceState, final boolean animated, 566 final Runnable onCompleteRunnable) { 567 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 568 PrivateTransitionCallbacks cb = 569 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 570 @Override 571 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 572 final View revealView, final View widgetsButtonView) { 573 return new AnimatorListenerAdapter() { 574 public void onAnimationEnd(Animator animation) { 575 // Hide the reveal view 576 revealView.setVisibility(View.INVISIBLE); 577 } 578 }; 579 } 580 @Override 581 void onTransitionComplete() { 582 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 583 } 584 }; 585 startAnimationToWorkspaceFromOverlay( 586 fromWorkspaceState, toWorkspaceState, 587 mLauncher.getWidgetsButton(), widgetsView, 588 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); 589 } 590 591 /** 592 * Starts an animation to the workspace from another workspace state, e.g. normal to overview. 593 */ 594 private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, 595 final Workspace.State toWorkspaceState, final boolean animated, 596 final Runnable onCompleteRunnable) { 597 final View fromWorkspace = mLauncher.getWorkspace(); 598 final HashMap<View, Integer> layerViews = new HashMap<>(); 599 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 600 final int revealDuration = mLauncher.getResources() 601 .getInteger(R.integer.config_overlayRevealTime); 602 603 // Cancel the current animation 604 cancelAnimation(); 605 606 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 607 608 playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null, 609 animated, animated, animation, layerViews); 610 611 if (animated) { 612 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 613 614 final AnimatorSet stateAnimation = animation; 615 final Runnable startAnimRunnable = new Runnable() { 616 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 617 public void run() { 618 // Check that mCurrentAnimation hasn't changed while 619 // we waited for a layout/draw pass 620 if (mCurrentAnimation != stateAnimation) 621 return; 622 623 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 624 625 // Enable all necessary layers 626 for (View v : layerViews.keySet()) { 627 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 628 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 629 } 630 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 631 v.buildLayer(); 632 } 633 } 634 stateAnimation.start(); 635 } 636 }; 637 animation.addListener(new AnimatorListenerAdapter() { 638 @Override 639 public void onAnimationEnd(Animator animation) { 640 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 641 642 // Run any queued runnables 643 if (onCompleteRunnable != null) { 644 onCompleteRunnable.run(); 645 } 646 647 // Disable all necessary layers 648 for (View v : layerViews.keySet()) { 649 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 650 v.setLayerType(View.LAYER_TYPE_NONE, null); 651 } 652 } 653 654 // This can hold unnecessary references to views. 655 cleanupAnimation(); 656 } 657 }); 658 fromWorkspace.post(startAnimRunnable); 659 mCurrentAnimation = animation; 660 } else /* if (!animated) */ { 661 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 662 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 663 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 664 665 // Run any queued runnables 666 if (onCompleteRunnable != null) { 667 onCompleteRunnable.run(); 668 } 669 670 mCurrentAnimation = null; 671 } 672 } 673 674 /** 675 * Creates and starts a new animation to the workspace. 676 */ 677 private void startAnimationToWorkspaceFromOverlay( 678 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 679 final View buttonView, final BaseContainerView fromView, 680 final boolean animated, int animType, final Runnable onCompleteRunnable, 681 final PrivateTransitionCallbacks pCb) { 682 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 683 final Resources res = mLauncher.getResources(); 684 final boolean material = Utilities.ATLEAST_LOLLIPOP; 685 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 686 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 687 final int itemsAlphaStagger = 688 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 689 690 final View toView = mLauncher.getWorkspace(); 691 final View revealView = fromView.getRevealView(); 692 final View contentView = fromView.getContentView(); 693 694 final HashMap<View, Integer> layerViews = new HashMap<>(); 695 696 // If for some reason our views aren't initialized, don't animate 697 boolean initialized = buttonView != null; 698 699 // Cancel the current animation 700 cancelAnimation(); 701 702 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 703 704 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 705 animated, initialized, animation, layerViews); 706 if (!animated || !initialized) { 707 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 708 fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 709 mAllAppsController.finishPullDown(); 710 } 711 fromView.setVisibility(View.GONE); 712 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 713 dispatchOnLauncherTransitionStart(fromView, animated, true); 714 dispatchOnLauncherTransitionEnd(fromView, animated, true); 715 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 716 dispatchOnLauncherTransitionStart(toView, animated, true); 717 dispatchOnLauncherTransitionEnd(toView, animated, true); 718 pCb.onTransitionComplete(); 719 720 // Run any queued runnables 721 if (onCompleteRunnable != null) { 722 onCompleteRunnable.run(); 723 } 724 return; 725 } 726 if (animType == CIRCULAR_REVEAL) { 727 // hideAppsCustomizeHelper is called in some cases when it is already hidden 728 // don't perform all these no-op animations. In particularly, this was causing 729 // the all-apps button to pop in and out. 730 if (fromView.getVisibility() == View.VISIBLE) { 731 int width = revealView.getMeasuredWidth(); 732 int height = revealView.getMeasuredHeight(); 733 float revealRadius = (float) Math.hypot(width / 2, height / 2); 734 revealView.setVisibility(View.VISIBLE); 735 revealView.setAlpha(1f); 736 revealView.setTranslationY(0); 737 layerViews.put(revealView, BUILD_AND_SET_LAYER); 738 739 // Calculate the final animation values 740 final float revealViewToXDrift; 741 final float revealViewToYDrift; 742 if (material) { 743 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 744 buttonView, null); 745 revealViewToYDrift = buttonViewToPanelDelta[1]; 746 revealViewToXDrift = buttonViewToPanelDelta[0]; 747 } else { 748 revealViewToYDrift = 2 * height / 3; 749 revealViewToXDrift = 0; 750 } 751 752 // The vertical motion of the apps panel should be delayed by one frame 753 // from the conceal animation in order to give the right feel. We correspondingly 754 // shorten the duration so that the slide and conceal end at the same time. 755 TimeInterpolator decelerateInterpolator = material ? 756 new LogDecelerateInterpolator(100, 0) : 757 new DecelerateInterpolator(1f); 758 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 759 0, revealViewToYDrift); 760 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 761 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 762 panelDriftY.setInterpolator(decelerateInterpolator); 763 animation.play(panelDriftY); 764 765 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 766 0, revealViewToXDrift); 767 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 768 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 769 panelDriftX.setInterpolator(decelerateInterpolator); 770 animation.play(panelDriftX); 771 772 // Setup animation for the reveal panel alpha 773 final float revealViewToAlpha = !material ? 0f : 774 pCb.materialRevealViewFinalAlpha; 775 if (revealViewToAlpha != 1f) { 776 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 777 1f, revealViewToAlpha); 778 panelAlpha.setDuration(material ? revealDuration : 150); 779 panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 780 panelAlpha.setInterpolator(decelerateInterpolator); 781 animation.play(panelAlpha); 782 } 783 784 // Setup the animation for the content view 785 layerViews.put(contentView, BUILD_AND_SET_LAYER); 786 787 // Create the individual animators 788 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 789 0, revealViewToYDrift); 790 contentView.setTranslationY(0); 791 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 792 pageDrift.setInterpolator(decelerateInterpolator); 793 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 794 animation.play(pageDrift); 795 796 contentView.setAlpha(1f); 797 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 798 itemsAlpha.setDuration(100); 799 itemsAlpha.setInterpolator(decelerateInterpolator); 800 animation.play(itemsAlpha); 801 802 // Invalidate the scrim throughout the animation to ensure the highlight 803 // cutout is correct throughout. 804 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); 805 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 806 @Override 807 public void onAnimationUpdate(ValueAnimator animation) { 808 mLauncher.getDragLayer().invalidateScrim(); 809 } 810 }); 811 animation.play(invalidateScrim); 812 813 if (material) { 814 // Animate the all apps button 815 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 816 AnimatorListenerAdapter listener = 817 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 818 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 819 revealRadius, finalRadius).createRevealAnimator(revealView); 820 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 821 reveal.setDuration(revealDuration); 822 reveal.setStartDelay(itemsAlphaStagger); 823 if (listener != null) { 824 reveal.addListener(listener); 825 } 826 animation.play(reveal); 827 } 828 } 829 830 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 831 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 832 833 animation.addListener(new AnimatorListenerAdapter() { 834 @Override 835 public void onAnimationEnd(Animator animation) { 836 fromView.setVisibility(View.GONE); 837 dispatchOnLauncherTransitionEnd(fromView, animated, true); 838 dispatchOnLauncherTransitionEnd(toView, animated, true); 839 840 // Run any queued runnables 841 if (onCompleteRunnable != null) { 842 onCompleteRunnable.run(); 843 } 844 845 // Disable all necessary layers 846 for (View v : layerViews.keySet()) { 847 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 848 v.setLayerType(View.LAYER_TYPE_NONE, null); 849 } 850 } 851 852 // Reset page transforms 853 if (contentView != null) { 854 contentView.setTranslationX(0); 855 contentView.setTranslationY(0); 856 contentView.setAlpha(1); 857 } 858 859 // This can hold unnecessary references to views. 860 cleanupAnimation(); 861 pCb.onTransitionComplete(); 862 } 863 }); 864 865 final AnimatorSet stateAnimation = animation; 866 final Runnable startAnimRunnable = new Runnable() { 867 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 868 public void run() { 869 // Check that mCurrentAnimation hasn't changed while 870 // we waited for a layout/draw pass 871 if (mCurrentAnimation != stateAnimation) 872 return; 873 874 dispatchOnLauncherTransitionStart(fromView, animated, false); 875 dispatchOnLauncherTransitionStart(toView, animated, false); 876 877 // Enable all necessary layers 878 for (View v : layerViews.keySet()) { 879 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 880 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 881 } 882 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 883 v.buildLayer(); 884 } 885 } 886 stateAnimation.start(); 887 } 888 }; 889 mCurrentAnimation = animation; 890 fromView.post(startAnimRunnable); 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 boolean shouldPost = 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 mCurrentAnimation = animation; 957 if (shouldPost) { 958 fromView.post(startAnimRunnable); 959 } else { 960 startAnimRunnable.run(); 961 } 962 } 963 return; 964 } 965 966 /** 967 * Dispatches the prepare-transition event to suitable views. 968 */ 969 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, 970 boolean multiplePagesVisible) { 971 if (v instanceof LauncherTransitionable) { 972 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 973 multiplePagesVisible); 974 } 975 } 976 977 /** 978 * Dispatches the start-transition event to suitable views. 979 */ 980 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 981 if (v instanceof LauncherTransitionable) { 982 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 983 toWorkspace); 984 } 985 986 // Update the workspace transition step as well 987 dispatchOnLauncherTransitionStep(v, 0f); 988 } 989 990 /** 991 * Dispatches the step-transition event to suitable views. 992 */ 993 void dispatchOnLauncherTransitionStep(View v, float t) { 994 if (v instanceof LauncherTransitionable) { 995 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 996 } 997 } 998 999 /** 1000 * Dispatches the end-transition event to suitable views. 1001 */ 1002 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 1003 if (v instanceof LauncherTransitionable) { 1004 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 1005 toWorkspace); 1006 } 1007 1008 // Update the workspace transition step as well 1009 dispatchOnLauncherTransitionStep(v, 1f); 1010 } 1011 1012 /** 1013 * Cancels the current animation. 1014 */ 1015 private void cancelAnimation() { 1016 if (mCurrentAnimation != null) { 1017 mCurrentAnimation.setDuration(0); 1018 mCurrentAnimation.cancel(); 1019 mCurrentAnimation = null; 1020 } 1021 } 1022 1023 @Thunk void cleanupAnimation() { 1024 mCurrentAnimation = null; 1025 } 1026} 1027