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