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