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