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