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