1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.animation.TimeInterpolator; 25 import android.animation.ValueAnimator; 26 import android.annotation.SuppressLint; 27 import android.content.res.Resources; 28 import android.util.Log; 29 import android.view.View; 30 import android.view.animation.AccelerateInterpolator; 31 import android.view.animation.DecelerateInterpolator; 32 33 import com.android.launcher3.allapps.AllAppsContainerView; 34 import com.android.launcher3.util.UiThreadCircularReveal; 35 import com.android.launcher3.util.Thunk; 36 import com.android.launcher3.widget.WidgetsContainerView; 37 38 import java.util.HashMap; 39 40 /** 41 * TODO: figure out what kind of tests we can write for this 42 * 43 * Things to test when changing the following class. 44 * - Home from workspace 45 * - from center screen 46 * - from other screens 47 * - Home from all apps 48 * - from center screen 49 * - from other screens 50 * - Back from all apps 51 * - from center screen 52 * - from other screens 53 * - Launch app from workspace and quit 54 * - with back 55 * - with home 56 * - Launch app from all apps and quit 57 * - with back 58 * - with home 59 * - Go to a screen that's not the default, then all 60 * apps, and launch and app, and go back 61 * - with back 62 * -with home 63 * - On workspace, long press power and go back 64 * - with back 65 * - with home 66 * - On all apps, long press power and go back 67 * - with back 68 * - with home 69 * - On workspace, power off 70 * - On all apps, power off 71 * - Launch an app and turn off the screen while in that app 72 * - Go back with home key 73 * - Go back with back key TODO: make this not go to workspace 74 * - From all apps 75 * - From workspace 76 * - Enter and exit car mode (becuase it causes an extra configuration changed) 77 * - From all apps 78 * - From the center workspace 79 * - From another workspace 80 */ 81 public class LauncherStateTransitionAnimation { 82 83 private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f; 84 85 /** 86 * Private callbacks made during transition setup. 87 */ 88 private static class PrivateTransitionCallbacks { 89 private final float materialRevealViewFinalAlpha; 90 91 PrivateTransitionCallbacks(float revealAlpha) { 92 materialRevealViewFinalAlpha = revealAlpha; 93 } 94 95 float getMaterialRevealViewStartFinalRadius() { 96 return 0; 97 } 98 AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, 99 View buttonView) { 100 return null; 101 } 102 void onTransitionComplete() {} 103 } 104 105 public static final String TAG = "LSTAnimation"; 106 107 // Flags to determine how to set the layers on views before the transition animation 108 public static final int BUILD_LAYER = 0; 109 public static final int BUILD_AND_SET_LAYER = 1; 110 public static final int SINGLE_FRAME_DELAY = 16; 111 112 @Thunk Launcher mLauncher; 113 @Thunk AnimatorSet mCurrentAnimation; 114 115 public LauncherStateTransitionAnimation(Launcher l) { 116 mLauncher = l; 117 } 118 119 /** 120 * Starts an animation to the apps view. 121 * 122 * @param startSearchAfterTransition Immediately starts app search after the transition to 123 * All Apps is completed. 124 */ 125 public void startAnimationToAllApps(final Workspace.State fromWorkspaceState, 126 final boolean animated, final boolean startSearchAfterTransition) { 127 final AllAppsContainerView toView = mLauncher.getAppsView(); 128 final View buttonView = mLauncher.getAllAppsButton(); 129 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 130 @Override 131 public float getMaterialRevealViewStartFinalRadius() { 132 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 133 return allAppsButtonSize / 2; 134 } 135 @Override 136 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 137 final View revealView, final View allAppsButtonView) { 138 return new AnimatorListenerAdapter() { 139 public void onAnimationStart(Animator animation) { 140 allAppsButtonView.setVisibility(View.INVISIBLE); 141 } 142 public void onAnimationEnd(Animator animation) { 143 allAppsButtonView.setVisibility(View.VISIBLE); 144 } 145 }; 146 } 147 @Override 148 void onTransitionComplete() { 149 if (startSearchAfterTransition) { 150 toView.startAppsSearch(); 151 } 152 } 153 }; 154 // Only animate the search bar if animating from spring loaded mode back to all apps 155 mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState, 156 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, cb); 157 } 158 159 /** 160 * Starts an animation to the widgets view. 161 */ 162 public void startAnimationToWidgets(final Workspace.State fromWorkspaceState, 163 final boolean animated) { 164 final WidgetsContainerView toView = mLauncher.getWidgetsView(); 165 final View buttonView = mLauncher.getWidgetsButton(); 166 167 mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState, 168 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, 169 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS)); 170 } 171 172 /** 173 * Starts and animation to the workspace from the current overlay view. 174 */ 175 public void startAnimationToWorkspace(final Launcher.State fromState, 176 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 177 final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) { 178 if (toWorkspaceState != Workspace.State.NORMAL && 179 toWorkspaceState != Workspace.State.SPRING_LOADED && 180 toWorkspaceState != Workspace.State.OVERVIEW) { 181 Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); 182 } 183 184 if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) { 185 startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage, 186 animated, onCompleteRunnable); 187 } else { 188 startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage, 189 animated, onCompleteRunnable); 190 } 191 } 192 193 /** 194 * Creates and starts a new animation to a particular overlay view. 195 */ 196 @SuppressLint("NewApi") 197 private AnimatorSet startAnimationToOverlay( 198 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 199 final View buttonView, final BaseContainerView toView, 200 final boolean animated, final PrivateTransitionCallbacks pCb) { 201 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 202 final Resources res = mLauncher.getResources(); 203 final boolean material = Utilities.ATLEAST_LOLLIPOP; 204 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 205 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 206 207 final View fromView = mLauncher.getWorkspace(); 208 209 final HashMap<View, Integer> layerViews = new HashMap<>(); 210 211 // If for some reason our views aren't initialized, don't animate 212 boolean initialized = buttonView != null; 213 214 // Cancel the current animation 215 cancelAnimation(); 216 217 // Create the workspace animation. 218 // NOTE: this call apparently also sets the state for the workspace if !animated 219 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1, 220 animated, layerViews); 221 222 // Animate the search bar 223 startWorkspaceSearchBarAnimation( 224 toWorkspaceState, animated ? revealDuration : 0, animation); 225 226 Animator updateTransitionStepAnim = dispatchOnLauncherTransitionStepAnim(fromView, toView); 227 228 final View contentView = toView.getContentView(); 229 230 if (animated && initialized) { 231 // Setup the reveal view animation 232 final View revealView = toView.getRevealView(); 233 234 int width = revealView.getMeasuredWidth(); 235 int height = revealView.getMeasuredHeight(); 236 float revealRadius = (float) Math.hypot(width / 2, height / 2); 237 revealView.setVisibility(View.VISIBLE); 238 revealView.setAlpha(0f); 239 revealView.setTranslationY(0f); 240 revealView.setTranslationX(0f); 241 242 // Calculate the final animation values 243 final float revealViewToAlpha; 244 final float revealViewToXDrift; 245 final float revealViewToYDrift; 246 if (material) { 247 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace( 248 revealView, buttonView, null); 249 revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 250 revealViewToYDrift = buttonViewToPanelDelta[1]; 251 revealViewToXDrift = buttonViewToPanelDelta[0]; 252 } else { 253 revealViewToAlpha = 0f; 254 revealViewToYDrift = 2 * height / 3; 255 revealViewToXDrift = 0; 256 } 257 258 // Create the animators 259 PropertyValuesHolder panelAlpha = 260 PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f); 261 PropertyValuesHolder panelDriftY = 262 PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0); 263 PropertyValuesHolder panelDriftX = 264 PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0); 265 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 266 panelAlpha, panelDriftY, panelDriftX); 267 panelAlphaAndDrift.setDuration(revealDuration); 268 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 269 270 // Play the animation 271 layerViews.put(revealView, BUILD_AND_SET_LAYER); 272 animation.play(panelAlphaAndDrift); 273 274 // Setup the animation for the content view 275 contentView.setVisibility(View.VISIBLE); 276 contentView.setAlpha(0f); 277 contentView.setTranslationY(revealViewToYDrift); 278 layerViews.put(contentView, BUILD_AND_SET_LAYER); 279 280 // Create the individual animators 281 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 282 revealViewToYDrift, 0); 283 pageDrift.setDuration(revealDuration); 284 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 285 pageDrift.setStartDelay(itemsAlphaStagger); 286 animation.play(pageDrift); 287 288 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 289 itemsAlpha.setDuration(revealDuration); 290 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 291 itemsAlpha.setStartDelay(itemsAlphaStagger); 292 animation.play(itemsAlpha); 293 294 if (material) { 295 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 296 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 297 revealView, buttonView); 298 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, 299 height / 2, startRadius, revealRadius); 300 reveal.setDuration(revealDuration); 301 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 302 if (listener != null) { 303 reveal.addListener(listener); 304 } 305 animation.play(reveal); 306 } 307 308 animation.addListener(new AnimatorListenerAdapter() { 309 @Override 310 public void onAnimationEnd(Animator animation) { 311 dispatchOnLauncherTransitionEnd(fromView, animated, false); 312 dispatchOnLauncherTransitionEnd(toView, animated, false); 313 314 // Hide the reveal view 315 revealView.setVisibility(View.INVISIBLE); 316 317 // Disable all necessary layers 318 for (View v : layerViews.keySet()) { 319 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 320 v.setLayerType(View.LAYER_TYPE_NONE, null); 321 } 322 } 323 324 // This can hold unnecessary references to views. 325 cleanupAnimation(); 326 pCb.onTransitionComplete(); 327 } 328 329 }); 330 331 // Play the workspace animation 332 if (workspaceAnim != null) { 333 animation.play(workspaceAnim); 334 } 335 336 animation.play(updateTransitionStepAnim); 337 338 // Dispatch the prepare transition signal 339 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 340 dispatchOnLauncherTransitionPrepare(toView, animated, false); 341 342 final AnimatorSet stateAnimation = animation; 343 final Runnable startAnimRunnable = new Runnable() { 344 public void run() { 345 // Check that mCurrentAnimation hasn't changed while 346 // we waited for a layout/draw pass 347 if (mCurrentAnimation != stateAnimation) 348 return; 349 dispatchOnLauncherTransitionStart(fromView, animated, false); 350 dispatchOnLauncherTransitionStart(toView, animated, false); 351 352 // Enable all necessary layers 353 for (View v : layerViews.keySet()) { 354 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 355 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 356 } 357 if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { 358 v.buildLayer(); 359 } 360 } 361 362 // Focus the new view 363 toView.requestFocus(); 364 365 stateAnimation.start(); 366 } 367 }; 368 toView.bringToFront(); 369 toView.setVisibility(View.VISIBLE); 370 toView.post(startAnimRunnable); 371 372 return animation; 373 } else { 374 toView.setTranslationX(0.0f); 375 toView.setTranslationY(0.0f); 376 toView.setScaleX(1.0f); 377 toView.setScaleY(1.0f); 378 toView.setVisibility(View.VISIBLE); 379 toView.bringToFront(); 380 381 // Show the content view 382 contentView.setVisibility(View.VISIBLE); 383 384 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 385 dispatchOnLauncherTransitionStart(fromView, animated, false); 386 dispatchOnLauncherTransitionEnd(fromView, animated, false); 387 dispatchOnLauncherTransitionPrepare(toView, animated, false); 388 dispatchOnLauncherTransitionStart(toView, animated, false); 389 dispatchOnLauncherTransitionEnd(toView, animated, false); 390 pCb.onTransitionComplete(); 391 392 return null; 393 } 394 } 395 396 /** 397 * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on 398 * {@param fromView} and {@param toView} as the animation interpolates. 399 * 400 * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener. 401 */ 402 private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) { 403 ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1); 404 updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 405 @Override 406 public void onAnimationUpdate(ValueAnimator animation) { 407 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction()); 408 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction()); 409 } 410 }); 411 return updateAnimator; 412 } 413 414 /** 415 * Starts and animation to the workspace from the apps view. 416 */ 417 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 418 final Workspace.State toWorkspaceState, final int toWorkspacePage, 419 final boolean animated, final Runnable onCompleteRunnable) { 420 AllAppsContainerView appsView = mLauncher.getAppsView(); 421 // No alpha anim from all apps 422 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 423 @Override 424 float getMaterialRevealViewStartFinalRadius() { 425 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 426 return allAppsButtonSize / 2; 427 } 428 @Override 429 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 430 final View revealView, final View allAppsButtonView) { 431 return new AnimatorListenerAdapter() { 432 public void onAnimationStart(Animator animation) { 433 // We set the alpha instead of visibility to ensure that the focus does not 434 // get taken from the all apps view 435 allAppsButtonView.setVisibility(View.VISIBLE); 436 allAppsButtonView.setAlpha(0f); 437 } 438 public void onAnimationEnd(Animator animation) { 439 // Hide the reveal view 440 revealView.setVisibility(View.INVISIBLE); 441 442 // Show the all apps button, and focus it 443 allAppsButtonView.setAlpha(1f); 444 } 445 }; 446 } 447 }; 448 // Only animate the search bar if animating to spring loaded mode from all apps 449 mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 450 toWorkspacePage, mLauncher.getAllAppsButton(), appsView, 451 animated, onCompleteRunnable, cb); 452 } 453 454 /** 455 * Starts and animation to the workspace from the widgets view. 456 */ 457 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 458 final Workspace.State toWorkspaceState, final int toWorkspacePage, 459 final boolean animated, final Runnable onCompleteRunnable) { 460 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 461 PrivateTransitionCallbacks cb = 462 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 463 @Override 464 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 465 final View revealView, final View widgetsButtonView) { 466 return new AnimatorListenerAdapter() { 467 public void onAnimationEnd(Animator animation) { 468 // Hide the reveal view 469 revealView.setVisibility(View.INVISIBLE); 470 } 471 }; 472 } 473 }; 474 mCurrentAnimation = startAnimationToWorkspaceFromOverlay( 475 fromWorkspaceState, toWorkspaceState, 476 toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView, 477 animated, onCompleteRunnable, cb); 478 } 479 480 /** 481 * Creates and starts a new animation to the workspace. 482 */ 483 private AnimatorSet startAnimationToWorkspaceFromOverlay( 484 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 485 final int toWorkspacePage, 486 final View buttonView, final BaseContainerView fromView, 487 final boolean animated, final Runnable onCompleteRunnable, 488 final PrivateTransitionCallbacks pCb) { 489 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 490 final Resources res = mLauncher.getResources(); 491 final boolean material = Utilities.ATLEAST_LOLLIPOP; 492 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 493 final int itemsAlphaStagger = 494 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 495 496 final View toView = mLauncher.getWorkspace(); 497 498 final HashMap<View, Integer> layerViews = new HashMap<>(); 499 500 // If for some reason our views aren't initialized, don't animate 501 boolean initialized = buttonView != null; 502 503 // Cancel the current animation 504 cancelAnimation(); 505 506 // Create the workspace animation. 507 // NOTE: this call apparently also sets the state for the workspace if !animated 508 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 509 toWorkspacePage, animated, layerViews); 510 511 // Animate the search bar 512 startWorkspaceSearchBarAnimation( 513 toWorkspaceState, animated ? revealDuration : 0, animation); 514 515 Animator updateTransitionStepAnim = dispatchOnLauncherTransitionStepAnim(fromView, toView); 516 517 if (animated && initialized) { 518 // Play the workspace animation 519 if (workspaceAnim != null) { 520 animation.play(workspaceAnim); 521 } 522 523 animation.play(updateTransitionStepAnim); 524 final View revealView = fromView.getRevealView(); 525 final View contentView = fromView.getContentView(); 526 527 // hideAppsCustomizeHelper is called in some cases when it is already hidden 528 // don't perform all these no-op animations. In particularly, this was causing 529 // the all-apps button to pop in and out. 530 if (fromView.getVisibility() == View.VISIBLE) { 531 int width = revealView.getMeasuredWidth(); 532 int height = revealView.getMeasuredHeight(); 533 float revealRadius = (float) Math.hypot(width / 2, height / 2); 534 revealView.setVisibility(View.VISIBLE); 535 revealView.setAlpha(1f); 536 revealView.setTranslationY(0); 537 layerViews.put(revealView, BUILD_AND_SET_LAYER); 538 539 // Calculate the final animation values 540 final float revealViewToXDrift; 541 final float revealViewToYDrift; 542 if (material) { 543 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 544 buttonView, null); 545 revealViewToYDrift = buttonViewToPanelDelta[1]; 546 revealViewToXDrift = buttonViewToPanelDelta[0]; 547 } else { 548 revealViewToYDrift = 2 * height / 3; 549 revealViewToXDrift = 0; 550 } 551 552 // The vertical motion of the apps panel should be delayed by one frame 553 // from the conceal animation in order to give the right feel. We correspondingly 554 // shorten the duration so that the slide and conceal end at the same time. 555 TimeInterpolator decelerateInterpolator = material ? 556 new LogDecelerateInterpolator(100, 0) : 557 new DecelerateInterpolator(1f); 558 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 559 0, revealViewToYDrift); 560 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 561 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 562 panelDriftY.setInterpolator(decelerateInterpolator); 563 animation.play(panelDriftY); 564 565 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 566 0, revealViewToXDrift); 567 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 568 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 569 panelDriftX.setInterpolator(decelerateInterpolator); 570 animation.play(panelDriftX); 571 572 // Setup animation for the reveal panel alpha 573 final float revealViewToAlpha = !material ? 0f : 574 pCb.materialRevealViewFinalAlpha; 575 if (revealViewToAlpha != 1f) { 576 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 577 1f, revealViewToAlpha); 578 panelAlpha.setDuration(material ? revealDuration : 150); 579 panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 580 panelAlpha.setInterpolator(decelerateInterpolator); 581 animation.play(panelAlpha); 582 } 583 584 // Setup the animation for the content view 585 layerViews.put(contentView, BUILD_AND_SET_LAYER); 586 587 // Create the individual animators 588 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 589 0, revealViewToYDrift); 590 contentView.setTranslationY(0); 591 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 592 pageDrift.setInterpolator(decelerateInterpolator); 593 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 594 animation.play(pageDrift); 595 596 contentView.setAlpha(1f); 597 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 598 itemsAlpha.setDuration(100); 599 itemsAlpha.setInterpolator(decelerateInterpolator); 600 animation.play(itemsAlpha); 601 602 if (material) { 603 // Animate the all apps button 604 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 605 AnimatorListenerAdapter listener = 606 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 607 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, 608 height / 2, revealRadius, finalRadius); 609 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 610 reveal.setDuration(revealDuration); 611 reveal.setStartDelay(itemsAlphaStagger); 612 if (listener != null) { 613 reveal.addListener(listener); 614 } 615 animation.play(reveal); 616 } 617 } 618 619 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 620 dispatchOnLauncherTransitionPrepare(toView, animated, true); 621 622 animation.addListener(new AnimatorListenerAdapter() { 623 @Override 624 public void onAnimationEnd(Animator animation) { 625 fromView.setVisibility(View.GONE); 626 dispatchOnLauncherTransitionEnd(fromView, animated, true); 627 dispatchOnLauncherTransitionEnd(toView, animated, true); 628 629 // Run any queued runnables 630 if (onCompleteRunnable != null) { 631 onCompleteRunnable.run(); 632 } 633 634 // Disable all necessary layers 635 for (View v : layerViews.keySet()) { 636 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 637 v.setLayerType(View.LAYER_TYPE_NONE, null); 638 } 639 } 640 641 // Reset page transforms 642 if (contentView != null) { 643 contentView.setTranslationX(0); 644 contentView.setTranslationY(0); 645 contentView.setAlpha(1); 646 } 647 648 // This can hold unnecessary references to views. 649 cleanupAnimation(); 650 pCb.onTransitionComplete(); 651 } 652 }); 653 654 final AnimatorSet stateAnimation = animation; 655 final Runnable startAnimRunnable = new Runnable() { 656 public void run() { 657 // Check that mCurrentAnimation hasn't changed while 658 // we waited for a layout/draw pass 659 if (mCurrentAnimation != stateAnimation) 660 return; 661 662 dispatchOnLauncherTransitionStart(fromView, animated, false); 663 dispatchOnLauncherTransitionStart(toView, animated, false); 664 665 // Enable all necessary layers 666 for (View v : layerViews.keySet()) { 667 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 668 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 669 } 670 if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { 671 v.buildLayer(); 672 } 673 } 674 stateAnimation.start(); 675 } 676 }; 677 fromView.post(startAnimRunnable); 678 679 return animation; 680 } else { 681 fromView.setVisibility(View.GONE); 682 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 683 dispatchOnLauncherTransitionStart(fromView, animated, true); 684 dispatchOnLauncherTransitionEnd(fromView, animated, true); 685 dispatchOnLauncherTransitionPrepare(toView, animated, true); 686 dispatchOnLauncherTransitionStart(toView, animated, true); 687 dispatchOnLauncherTransitionEnd(toView, animated, true); 688 pCb.onTransitionComplete(); 689 690 // Run any queued runnables 691 if (onCompleteRunnable != null) { 692 onCompleteRunnable.run(); 693 } 694 695 return null; 696 } 697 } 698 699 /** 700 * Coordinates the workspace search bar animation along with the launcher state animation. 701 */ 702 private void startWorkspaceSearchBarAnimation( 703 final Workspace.State toWorkspaceState, int duration, AnimatorSet animation) { 704 final SearchDropTargetBar.State toSearchBarState = 705 toWorkspaceState.searchDropTargetBarState; 706 mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration, animation); 707 } 708 709 /** 710 * Dispatches the prepare-transition event to suitable views. 711 */ 712 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { 713 if (v instanceof LauncherTransitionable) { 714 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 715 toWorkspace); 716 } 717 } 718 719 /** 720 * Dispatches the start-transition event to suitable views. 721 */ 722 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 723 if (v instanceof LauncherTransitionable) { 724 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 725 toWorkspace); 726 } 727 728 // Update the workspace transition step as well 729 dispatchOnLauncherTransitionStep(v, 0f); 730 } 731 732 /** 733 * Dispatches the step-transition event to suitable views. 734 */ 735 void dispatchOnLauncherTransitionStep(View v, float t) { 736 if (v instanceof LauncherTransitionable) { 737 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 738 } 739 } 740 741 /** 742 * Dispatches the end-transition event to suitable views. 743 */ 744 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 745 if (v instanceof LauncherTransitionable) { 746 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 747 toWorkspace); 748 } 749 750 // Update the workspace transition step as well 751 dispatchOnLauncherTransitionStep(v, 1f); 752 } 753 754 /** 755 * Cancels the current animation. 756 */ 757 private void cancelAnimation() { 758 if (mCurrentAnimation != null) { 759 mCurrentAnimation.setDuration(0); 760 mCurrentAnimation.cancel(); 761 mCurrentAnimation = null; 762 } 763 } 764 765 @Thunk void cleanupAnimation() { 766 mCurrentAnimation = null; 767 } 768 } 769