LauncherStateTransitionAnimation.java revision 5d2fc32e6da66f877dfba4fe513fbabdcdae5f99
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.content.res.Resources; 27import android.util.Log; 28import android.view.View; 29import android.view.animation.AccelerateInterpolator; 30import android.view.animation.DecelerateInterpolator; 31 32import com.android.launcher3.allapps.AllAppsContainerView; 33import com.android.launcher3.util.Thunk; 34import com.android.launcher3.util.UiThreadCircularReveal; 35import com.android.launcher3.widget.WidgetsContainerView; 36 37import 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 */ 80public 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 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { 174 @Override 175 public float getMaterialRevealViewFinalAlpha(View revealView) { 176 return 0.3f; 177 } 178 }; 179 startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, 180 toView.getContentView(), toView.getRevealView(), null, animated, 181 true /* hideSearchBar */, cb); 182 } 183 184 /** 185 * Starts and animation to the workspace from the current overlay view. 186 */ 187 public void startAnimationToWorkspace(final Launcher.State fromState, 188 final Workspace.State toWorkspaceState, 189 final boolean animated, final Runnable onCompleteRunnable) { 190 if (toWorkspaceState != Workspace.State.NORMAL && 191 toWorkspaceState != Workspace.State.SPRING_LOADED && 192 toWorkspaceState != Workspace.State.OVERVIEW) { 193 Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); 194 } 195 196 if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) { 197 startAnimationToWorkspaceFromAllApps(toWorkspaceState, animated, onCompleteRunnable); 198 } else { 199 startAnimationToWorkspaceFromWidgets(toWorkspaceState, animated, onCompleteRunnable); 200 } 201 } 202 203 /** 204 * Creates and starts a new animation to a particular overlay view. 205 */ 206 @SuppressLint("NewApi") 207 private void startAnimationToOverlay(final Workspace.State toWorkspaceState, 208 final View buttonView, final View toView, final View contentView, final View revealView, 209 final View overlaySearchBarView, final boolean animated, final boolean hideSearchBar, 210 final PrivateTransitionCallbacks pCb) { 211 final Resources res = mLauncher.getResources(); 212 final boolean material = Utilities.isLmpOrAbove(); 213 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 214 final int itemsAlphaStagger = 215 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 216 217 final View fromView = mLauncher.getWorkspace(); 218 219 final HashMap<View, Integer> layerViews = new HashMap<>(); 220 221 // If for some reason our views aren't initialized, don't animate 222 boolean initialized = buttonView != null; 223 224 // Cancel the current animation 225 cancelAnimation(); 226 227 // Create the workspace animation. 228 // NOTE: this call apparently also sets the state for the workspace if !animated 229 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 230 animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews); 231 232 if (animated && initialized) { 233 mStateAnimation = LauncherAnimUtils.createAnimatorSet(); 234 235 // Setup the reveal view animation 236 int width = revealView.getMeasuredWidth(); 237 int height = revealView.getMeasuredHeight(); 238 float revealRadius = (float) Math.hypot(width / 2, height / 2); 239 revealView.setVisibility(View.VISIBLE); 240 revealView.setAlpha(0f); 241 revealView.setTranslationY(0f); 242 revealView.setTranslationX(0f); 243 244 // Calculate the final animation values 245 final float revealViewToAlpha; 246 final float revealViewToXDrift; 247 final float revealViewToYDrift; 248 if (material) { 249 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 250 buttonView, null); 251 revealViewToAlpha = pCb.getMaterialRevealViewFinalAlpha(revealView); 252 revealViewToYDrift = buttonViewToPanelDelta[1]; 253 revealViewToXDrift = buttonViewToPanelDelta[0]; 254 } else { 255 revealViewToAlpha = 0f; 256 revealViewToYDrift = 2 * height / 3; 257 revealViewToXDrift = 0; 258 } 259 260 // Create the animators 261 PropertyValuesHolder panelAlpha = 262 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 263 PropertyValuesHolder panelDriftY = 264 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 265 PropertyValuesHolder panelDriftX = 266 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 267 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 268 panelAlpha, panelDriftY, panelDriftX); 269 panelAlphaAndDrift.setDuration(revealDuration); 270 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 271 272 // Play the animation 273 layerViews.put(revealView, BUILD_AND_SET_LAYER); 274 mStateAnimation.play(panelAlphaAndDrift); 275 276 if (overlaySearchBarView != null) { 277 overlaySearchBarView.setAlpha(0f); 278 ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 0f, 1f); 279 searchBarAlpha.setDuration(100); 280 searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 281 layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER); 282 mStateAnimation.play(searchBarAlpha); 283 } 284 285 // Setup the animation for the content view 286 contentView.setVisibility(View.VISIBLE); 287 contentView.setAlpha(0f); 288 contentView.setTranslationY(revealViewToYDrift); 289 layerViews.put(contentView, BUILD_AND_SET_LAYER); 290 291 // Create the individual animators 292 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 293 revealViewToYDrift, 0); 294 pageDrift.setDuration(revealDuration); 295 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 296 pageDrift.setStartDelay(itemsAlphaStagger); 297 mStateAnimation.play(pageDrift); 298 299 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 300 itemsAlpha.setDuration(revealDuration); 301 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 302 itemsAlpha.setStartDelay(itemsAlphaStagger); 303 mStateAnimation.play(itemsAlpha); 304 305 if (material) { 306 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 307 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 308 revealView, buttonView); 309 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, 310 height / 2, startRadius, revealRadius); 311 reveal.setDuration(revealDuration); 312 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 313 if (listener != null) { 314 reveal.addListener(listener); 315 } 316 mStateAnimation.play(reveal); 317 } 318 319 mStateAnimation.addListener(new AnimatorListenerAdapter() { 320 @Override 321 public void onAnimationEnd(Animator animation) { 322 dispatchOnLauncherTransitionEnd(fromView, animated, false); 323 dispatchOnLauncherTransitionEnd(toView, animated, false); 324 325 // Hide the reveal view 326 revealView.setVisibility(View.INVISIBLE); 327 328 // Disable all necessary layers 329 for (View v : layerViews.keySet()) { 330 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 331 v.setLayerType(View.LAYER_TYPE_NONE, null); 332 } 333 } 334 335 if (hideSearchBar) { 336 mCb.onStateTransitionHideSearchBar(); 337 } 338 339 // This can hold unnecessary references to views. 340 mStateAnimation = null; 341 pCb.onTransitionComplete(); 342 } 343 344 }); 345 346 // Play the workspace animation 347 if (workspaceAnim != null) { 348 mStateAnimation.play(workspaceAnim); 349 } 350 351 // Dispatch the prepare transition signal 352 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 353 dispatchOnLauncherTransitionPrepare(toView, animated, false); 354 355 356 final AnimatorSet stateAnimation = mStateAnimation; 357 final Runnable startAnimRunnable = new Runnable() { 358 public void run() { 359 // Check that mStateAnimation hasn't changed while 360 // we waited for a layout/draw pass 361 if (mStateAnimation != stateAnimation) 362 return; 363 dispatchOnLauncherTransitionStart(fromView, animated, false); 364 dispatchOnLauncherTransitionStart(toView, animated, false); 365 366 // Enable all necessary layers 367 boolean isLmpOrAbove = Utilities.isLmpOrAbove(); 368 for (View v : layerViews.keySet()) { 369 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 370 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 371 } 372 if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) { 373 v.buildLayer(); 374 } 375 } 376 377 // Focus the new view 378 toView.requestFocus(); 379 380 mStateAnimation.start(); 381 } 382 }; 383 toView.bringToFront(); 384 toView.setVisibility(View.VISIBLE); 385 toView.post(startAnimRunnable); 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 if (hideSearchBar) { 398 mCb.onStateTransitionHideSearchBar(); 399 } 400 401 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 402 dispatchOnLauncherTransitionStart(fromView, animated, false); 403 dispatchOnLauncherTransitionEnd(fromView, animated, false); 404 dispatchOnLauncherTransitionPrepare(toView, animated, false); 405 dispatchOnLauncherTransitionStart(toView, animated, false); 406 dispatchOnLauncherTransitionEnd(toView, animated, false); 407 pCb.onTransitionComplete(); 408 } 409 } 410 411 /** 412 * Starts and animation to the workspace from the apps view. 413 */ 414 private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState, 415 final boolean animated, final Runnable onCompleteRunnable) { 416 AllAppsContainerView appsView = mLauncher.getAppsView(); 417 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { 418 int[] mAllAppsToPanelDelta; 419 420 @Override 421 float getMaterialRevealViewFinalAlpha(View revealView) { 422 // No alpha anim from all apps 423 return 1f; 424 } 425 @Override 426 float getMaterialRevealViewStartFinalRadius() { 427 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 428 return allAppsButtonSize / 2; 429 } 430 @Override 431 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 432 final View revealView, final View allAppsButtonView) { 433 return new AnimatorListenerAdapter() { 434 public void onAnimationStart(Animator animation) { 435 // We set the alpha instead of visibility to ensure that the focus does not 436 // get taken from the all apps view 437 allAppsButtonView.setVisibility(View.VISIBLE); 438 allAppsButtonView.setAlpha(0f); 439 } 440 public void onAnimationEnd(Animator animation) { 441 // Hide the reveal view 442 revealView.setVisibility(View.INVISIBLE); 443 444 // Show the all apps button, and focus it 445 allAppsButtonView.setAlpha(1f); 446 } 447 }; 448 } 449 }; 450 // Only animate the search bar if animating to spring loaded mode from all apps 451 startAnimationToWorkspaceFromOverlay(toWorkspaceState, mLauncher.getAllAppsButton(), 452 appsView, appsView.getContentView(), appsView.getRevealView(), 453 appsView.getSearchBarView(), animated, onCompleteRunnable, cb); 454 } 455 456 /** 457 * Starts and animation to the workspace from the widgets view. 458 */ 459 private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState, 460 final boolean animated, final Runnable onCompleteRunnable) { 461 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 462 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { 463 @Override 464 float getMaterialRevealViewFinalAlpha(View revealView) { 465 return 0.3f; 466 } 467 @Override 468 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 469 final View revealView, final View widgetsButtonView) { 470 return new AnimatorListenerAdapter() { 471 public void onAnimationEnd(Animator animation) { 472 // Hide the reveal view 473 revealView.setVisibility(View.INVISIBLE); 474 } 475 }; 476 } 477 }; 478 startAnimationToWorkspaceFromOverlay(toWorkspaceState, mLauncher.getWidgetsButton(), 479 widgetsView, widgetsView.getContentView(), widgetsView.getRevealView(), null, 480 animated, onCompleteRunnable, cb); 481 } 482 483 /** 484 * Creates and starts a new animation to the workspace. 485 */ 486 private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState, 487 final View buttonView, final View fromView, final View contentView, 488 final View revealView, final View overlaySearchBarView, final boolean animated, 489 final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb) { 490 final Resources res = mLauncher.getResources(); 491 final boolean material = Utilities.isLmpOrAbove(); 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 animated, overlaySearchBarView != null /* hasOverlaySearchBar */, 510 layerViews); 511 512 if (animated && initialized) { 513 mStateAnimation = LauncherAnimUtils.createAnimatorSet(); 514 515 // Play the workspace animation 516 if (workspaceAnim != null) { 517 mStateAnimation.play(workspaceAnim); 518 } 519 520 // hideAppsCustomizeHelper is called in some cases when it is already hidden 521 // don't perform all these no-op animations. In particularly, this was causing 522 // the all-apps button to pop in and out. 523 if (fromView.getVisibility() == View.VISIBLE) { 524 int width = revealView.getMeasuredWidth(); 525 int height = revealView.getMeasuredHeight(); 526 float revealRadius = (float) Math.hypot(width / 2, height / 2); 527 revealView.setVisibility(View.VISIBLE); 528 revealView.setAlpha(1f); 529 revealView.setTranslationY(0); 530 layerViews.put(revealView, BUILD_AND_SET_LAYER); 531 532 // Calculate the final animation values 533 final float revealViewToXDrift; 534 final float revealViewToYDrift; 535 if (material) { 536 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 537 buttonView, null); 538 revealViewToYDrift = buttonViewToPanelDelta[1]; 539 revealViewToXDrift = buttonViewToPanelDelta[0]; 540 } else { 541 revealViewToYDrift = 2 * height / 3; 542 revealViewToXDrift = 0; 543 } 544 545 // The vertical motion of the apps panel should be delayed by one frame 546 // from the conceal animation in order to give the right feel. We correspondingly 547 // shorten the duration so that the slide and conceal end at the same time. 548 TimeInterpolator decelerateInterpolator = material ? 549 new LogDecelerateInterpolator(100, 0) : 550 new DecelerateInterpolator(1f); 551 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 552 0, revealViewToYDrift); 553 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 554 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 555 panelDriftY.setInterpolator(decelerateInterpolator); 556 mStateAnimation.play(panelDriftY); 557 558 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 559 0, revealViewToXDrift); 560 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 561 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 562 panelDriftX.setInterpolator(decelerateInterpolator); 563 mStateAnimation.play(panelDriftX); 564 565 // Setup animation for the reveal panel alpha 566 final float revealViewToAlpha = !material ? 0f : 567 pCb.getMaterialRevealViewFinalAlpha(revealView); 568 if (revealViewToAlpha != 1f) { 569 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 570 1f, revealViewToAlpha); 571 panelAlpha.setDuration(material ? revealDuration : 150); 572 panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 573 panelAlpha.setInterpolator(decelerateInterpolator); 574 mStateAnimation.play(panelAlpha); 575 } 576 577 // Setup the animation for the content view 578 layerViews.put(contentView, BUILD_AND_SET_LAYER); 579 580 // Create the individual animators 581 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 582 0, revealViewToYDrift); 583 contentView.setTranslationY(0); 584 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 585 pageDrift.setInterpolator(decelerateInterpolator); 586 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 587 mStateAnimation.play(pageDrift); 588 589 contentView.setAlpha(1f); 590 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 591 itemsAlpha.setDuration(100); 592 itemsAlpha.setInterpolator(decelerateInterpolator); 593 mStateAnimation.play(itemsAlpha); 594 595 if (overlaySearchBarView != null) { 596 overlaySearchBarView.setAlpha(1f); 597 ObjectAnimator searchAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 1f, 0f); 598 searchAlpha.setDuration(material ? 100 : 150); 599 searchAlpha.setInterpolator(decelerateInterpolator); 600 searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 601 layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER); 602 mStateAnimation.play(searchAlpha); 603 } 604 605 if (material) { 606 // Animate the all apps button 607 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 608 AnimatorListenerAdapter listener = 609 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 610 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, 611 height / 2, revealRadius, finalRadius); 612 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 613 reveal.setDuration(revealDuration); 614 reveal.setStartDelay(itemsAlphaStagger); 615 if (listener != null) { 616 reveal.addListener(listener); 617 } 618 mStateAnimation.play(reveal); 619 } 620 621 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 622 dispatchOnLauncherTransitionPrepare(toView, animated, true); 623 } 624 625 mStateAnimation.addListener(new AnimatorListenerAdapter() { 626 @Override 627 public void onAnimationEnd(Animator animation) { 628 fromView.setVisibility(View.GONE); 629 dispatchOnLauncherTransitionEnd(fromView, animated, true); 630 dispatchOnLauncherTransitionEnd(toView, animated, true); 631 632 // Run any queued runnables 633 if (onCompleteRunnable != null) { 634 onCompleteRunnable.run(); 635 } 636 637 // Disable all necessary layers 638 for (View v : layerViews.keySet()) { 639 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 640 v.setLayerType(View.LAYER_TYPE_NONE, null); 641 } 642 } 643 644 // Reset page transforms 645 if (contentView != null) { 646 contentView.setTranslationX(0); 647 contentView.setTranslationY(0); 648 contentView.setAlpha(1); 649 } 650 if (overlaySearchBarView != null) { 651 overlaySearchBarView.setAlpha(1f); 652 } 653 654 // This can hold unnecessary references to views. 655 mStateAnimation = null; 656 pCb.onTransitionComplete(); 657 } 658 }); 659 660 final AnimatorSet stateAnimation = mStateAnimation; 661 final Runnable startAnimRunnable = new Runnable() { 662 public void run() { 663 // Check that mStateAnimation hasn't changed while 664 // we waited for a layout/draw pass 665 if (mStateAnimation != stateAnimation) 666 return; 667 dispatchOnLauncherTransitionStart(fromView, animated, false); 668 dispatchOnLauncherTransitionStart(toView, animated, false); 669 670 // Enable all necessary layers 671 boolean isLmpOrAbove = Utilities.isLmpOrAbove(); 672 for (View v : layerViews.keySet()) { 673 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 674 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 675 } 676 if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) { 677 v.buildLayer(); 678 } 679 } 680 mStateAnimation.start(); 681 } 682 }; 683 fromView.post(startAnimRunnable); 684 } else { 685 fromView.setVisibility(View.GONE); 686 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 687 dispatchOnLauncherTransitionStart(fromView, animated, true); 688 dispatchOnLauncherTransitionEnd(fromView, animated, true); 689 dispatchOnLauncherTransitionPrepare(toView, animated, true); 690 dispatchOnLauncherTransitionStart(toView, animated, true); 691 dispatchOnLauncherTransitionEnd(toView, animated, true); 692 pCb.onTransitionComplete(); 693 694 // Run any queued runnables 695 if (onCompleteRunnable != null) { 696 onCompleteRunnable.run(); 697 } 698 } 699 } 700 701 702 /** 703 * Dispatches the prepare-transition event to suitable views. 704 */ 705 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { 706 if (v instanceof LauncherTransitionable) { 707 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 708 toWorkspace); 709 } 710 } 711 712 /** 713 * Dispatches the start-transition event to suitable views. 714 */ 715 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 716 if (v instanceof LauncherTransitionable) { 717 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 718 toWorkspace); 719 } 720 721 // Update the workspace transition step as well 722 dispatchOnLauncherTransitionStep(v, 0f); 723 } 724 725 /** 726 * Dispatches the step-transition event to suitable views. 727 */ 728 void dispatchOnLauncherTransitionStep(View v, float t) { 729 if (v instanceof LauncherTransitionable) { 730 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 731 } 732 } 733 734 /** 735 * Dispatches the end-transition event to suitable views. 736 */ 737 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 738 if (v instanceof LauncherTransitionable) { 739 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 740 toWorkspace); 741 } 742 743 // Update the workspace transition step as well 744 dispatchOnLauncherTransitionStep(v, 1f); 745 } 746 747 /** 748 * Cancels the current animation. 749 */ 750 private void cancelAnimation() { 751 if (mStateAnimation != null) { 752 mStateAnimation.setDuration(0); 753 mStateAnimation.cancel(); 754 mStateAnimation = null; 755 } 756 } 757} 758