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