Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.animation.TimeInterpolator;
     24 import android.animation.ValueAnimator;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.accessibility.AccessibilityManager;
     30 import android.view.accessibility.AccessibilityNodeInfo;
     31 import android.view.animation.DecelerateInterpolator;
     32 
     33 import com.android.launcher3.config.FeatureFlags;
     34 import com.android.launcher3.dragndrop.DragLayer;
     35 import com.android.launcher3.util.Thunk;
     36 
     37 import java.util.HashMap;
     38 
     39 /**
     40  * A convenience class to update a view's visibility state after an alpha animation.
     41  */
     42 class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimator.AnimatorUpdateListener {
     43     private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
     44 
     45     private View mView;
     46     private boolean mAccessibilityEnabled;
     47     private boolean mCanceled = false;
     48 
     49     public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
     50         mView = v;
     51         mAccessibilityEnabled = accessibilityEnabled;
     52     }
     53 
     54     @Override
     55     public void onAnimationUpdate(ValueAnimator arg0) {
     56         updateVisibility(mView, mAccessibilityEnabled);
     57     }
     58 
     59     public static void updateVisibility(View view, boolean accessibilityEnabled) {
     60         // We want to avoid the extra layout pass by setting the views to GONE unless
     61         // accessibility is on, in which case not setting them to GONE causes a glitch.
     62         int invisibleState = accessibilityEnabled ? View.GONE : View.INVISIBLE;
     63         if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
     64             view.setVisibility(invisibleState);
     65         } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
     66                 && view.getVisibility() != View.VISIBLE) {
     67             view.setVisibility(View.VISIBLE);
     68         }
     69     }
     70 
     71     @Override
     72     public void onAnimationCancel(Animator animation) {
     73         mCanceled = true;
     74     }
     75 
     76     @Override
     77     public void onAnimationEnd(Animator arg0) {
     78         if (mCanceled) return;
     79         updateVisibility(mView, mAccessibilityEnabled);
     80     }
     81 
     82     @Override
     83     public void onAnimationStart(Animator arg0) {
     84         // We want the views to be visible for animation, so fade-in/out is visible
     85         mView.setVisibility(View.VISIBLE);
     86     }
     87 }
     88 
     89 /**
     90  * This interpolator emulates the rate at which the perceived scale of an object changes
     91  * as its distance from a camera increases. When this interpolator is applied to a scale
     92  * animation on a view, it evokes the sense that the object is shrinking due to moving away
     93  * from the camera.
     94  */
     95 class ZInterpolator implements TimeInterpolator {
     96     private float focalLength;
     97 
     98     public ZInterpolator(float foc) {
     99         focalLength = foc;
    100     }
    101 
    102     public float getInterpolation(float input) {
    103         return (1.0f - focalLength / (focalLength + input)) /
    104                 (1.0f - focalLength / (focalLength + 1.0f));
    105     }
    106 }
    107 
    108 /**
    109  * The exact reverse of ZInterpolator.
    110  */
    111 class InverseZInterpolator implements TimeInterpolator {
    112     private ZInterpolator zInterpolator;
    113     public InverseZInterpolator(float foc) {
    114         zInterpolator = new ZInterpolator(foc);
    115     }
    116     public float getInterpolation(float input) {
    117         return 1 - zInterpolator.getInterpolation(1 - input);
    118     }
    119 }
    120 
    121 /**
    122  * InverseZInterpolator compounded with an ease-out.
    123  */
    124 class ZoomInInterpolator implements TimeInterpolator {
    125     private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
    126     private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
    127 
    128     public float getInterpolation(float input) {
    129         return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
    130     }
    131 }
    132 
    133 /**
    134  * Stores the transition states for convenience.
    135  */
    136 class TransitionStates {
    137 
    138     // Raw states
    139     final boolean oldStateIsNormal;
    140     final boolean oldStateIsSpringLoaded;
    141     final boolean oldStateIsNormalHidden;
    142     final boolean oldStateIsOverviewHidden;
    143     final boolean oldStateIsOverview;
    144 
    145     final boolean stateIsNormal;
    146     final boolean stateIsSpringLoaded;
    147     final boolean stateIsNormalHidden;
    148     final boolean stateIsOverviewHidden;
    149     final boolean stateIsOverview;
    150 
    151     // Convenience members
    152     final boolean workspaceToAllApps;
    153     final boolean overviewToAllApps;
    154     final boolean allAppsToWorkspace;
    155     final boolean workspaceToOverview;
    156     final boolean overviewToWorkspace;
    157 
    158     public TransitionStates(final Workspace.State fromState, final Workspace.State toState) {
    159         oldStateIsNormal = (fromState == Workspace.State.NORMAL);
    160         oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
    161         oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
    162         oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
    163         oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
    164 
    165         stateIsNormal = (toState == Workspace.State.NORMAL);
    166         stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
    167         stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
    168         stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
    169         stateIsOverview = (toState == Workspace.State.OVERVIEW);
    170 
    171         workspaceToOverview = (oldStateIsNormal && stateIsOverview);
    172         workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
    173         overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
    174         overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
    175         allAppsToWorkspace = (oldStateIsNormalHidden && stateIsNormal);
    176     }
    177 }
    178 
    179 /**
    180  * Manages the animations between each of the workspace states.
    181  */
    182 public class WorkspaceStateTransitionAnimation {
    183 
    184     public static final String TAG = "WorkspaceStateTransitionAnimation";
    185 
    186     @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350;
    187 
    188     final @Thunk Launcher mLauncher;
    189     final @Thunk Workspace mWorkspace;
    190 
    191     @Thunk AnimatorSet mStateAnimator;
    192 
    193     @Thunk float mCurrentScale;
    194     @Thunk float mNewScale;
    195 
    196     @Thunk final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
    197 
    198     @Thunk float mSpringLoadedShrinkFactor;
    199     @Thunk float mOverviewModeShrinkFactor;
    200     @Thunk float mWorkspaceScrimAlpha;
    201     @Thunk int mAllAppsTransitionTime;
    202     @Thunk int mOverviewTransitionTime;
    203     @Thunk int mOverlayTransitionTime;
    204     @Thunk int mSpringLoadedTransitionTime;
    205     @Thunk boolean mWorkspaceFadeInAdjacentScreens;
    206 
    207     public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
    208         mLauncher = launcher;
    209         mWorkspace = workspace;
    210 
    211         DeviceProfile grid = mLauncher.getDeviceProfile();
    212         Resources res = launcher.getResources();
    213         mAllAppsTransitionTime = res.getInteger(R.integer.config_allAppsTransitionTime);
    214         mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime);
    215         mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
    216         mSpringLoadedTransitionTime = mOverlayTransitionTime / 2;
    217         mSpringLoadedShrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
    218         mOverviewModeShrinkFactor =
    219                 res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
    220         mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
    221         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
    222     }
    223 
    224     public void snapToPageFromOverView(int whichPage) {
    225         mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator);
    226     }
    227 
    228     public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
    229             boolean animated, HashMap<View, Integer> layerViews) {
    230         AccessibilityManager am = (AccessibilityManager)
    231                 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
    232         final boolean accessibilityEnabled = am.isEnabled();
    233         TransitionStates states = new TransitionStates(fromState, toState);
    234         int workspaceDuration = getAnimationDuration(states);
    235         animateWorkspace(states, animated, workspaceDuration, layerViews,
    236                 accessibilityEnabled);
    237         animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
    238         return mStateAnimator;
    239     }
    240 
    241     public float getFinalScale() {
    242         return mNewScale;
    243     }
    244 
    245     /**
    246      * Returns the proper animation duration for a transition.
    247      */
    248     private int getAnimationDuration(TransitionStates states) {
    249         if (states.workspaceToAllApps || states.overviewToAllApps) {
    250             return mAllAppsTransitionTime;
    251         } else if (states.workspaceToOverview || states.overviewToWorkspace) {
    252             return mOverviewTransitionTime;
    253         } else if (mLauncher.mState == Launcher.State.WORKSPACE_SPRING_LOADED
    254                 || states.oldStateIsNormal && states.stateIsSpringLoaded) {
    255             return mSpringLoadedTransitionTime;
    256         } else {
    257             return mOverlayTransitionTime;
    258         }
    259     }
    260 
    261     /**
    262      * Starts a transition animation for the workspace.
    263      */
    264     private void animateWorkspace(final TransitionStates states, final boolean animated,
    265                                   final int duration, final HashMap<View, Integer> layerViews,
    266                                   final boolean accessibilityEnabled) {
    267         // Cancel existing workspace animations and create a new animator set if requested
    268         cancelAnimation();
    269         if (animated) {
    270             mStateAnimator = LauncherAnimUtils.createAnimatorSet();
    271         }
    272 
    273         // Update the workspace state
    274         float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
    275                 1.0f : 0f;
    276         float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded ||
    277                 (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f;
    278         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
    279         float finalQsbAlpha = (states.stateIsNormal ||
    280                 (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f;
    281 
    282         float finalWorkspaceTranslationY = 0;
    283         if (states.stateIsOverview || states.stateIsOverviewHidden) {
    284             finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
    285         } else if (states.stateIsSpringLoaded) {
    286             finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
    287         }
    288 
    289         final int childCount = mWorkspace.getChildCount();
    290         final int customPageCount = mWorkspace.numCustomPages();
    291 
    292         mNewScale = 1.0f;
    293 
    294         if (states.oldStateIsOverview) {
    295             mWorkspace.disableFreeScroll();
    296         } else if (states.stateIsOverview) {
    297             mWorkspace.enableFreeScroll();
    298         }
    299 
    300         if (!states.stateIsNormal) {
    301             if (states.stateIsSpringLoaded) {
    302                 mNewScale = mSpringLoadedShrinkFactor;
    303             } else if (states.stateIsOverview || states.stateIsOverviewHidden) {
    304                 mNewScale = mOverviewModeShrinkFactor;
    305             }
    306         }
    307 
    308         int toPage = mWorkspace.getPageNearestToCenterOfScreen();
    309         // TODO: Animate the celllayout alpha instead of the pages.
    310         for (int i = 0; i < childCount; i++) {
    311             final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
    312             float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
    313             float finalAlpha;
    314             if (states.stateIsOverviewHidden) {
    315                 finalAlpha = 0f;
    316             } else if(states.stateIsNormalHidden) {
    317                 finalAlpha = (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
    318                         i == mWorkspace.getNextPage()) ? 1 : 0;
    319             } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
    320                 finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
    321             } else {
    322                 finalAlpha = 1f;
    323             }
    324 
    325             // If we are animating to/from the small state, then hide the side pages and fade the
    326             // current page in
    327             if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !mWorkspace.isSwitchingState()) {
    328                 if (states.workspaceToAllApps || states.allAppsToWorkspace) {
    329                     boolean isCurrentPage = (i == toPage);
    330                     if (states.allAppsToWorkspace && isCurrentPage) {
    331                         initialAlpha = 0f;
    332                     } else if (!isCurrentPage) {
    333                         initialAlpha = finalAlpha = 0f;
    334                     }
    335                     cl.setShortcutAndWidgetAlpha(initialAlpha);
    336                 }
    337             }
    338 
    339             if (animated) {
    340                 float oldBackgroundAlpha = cl.getBackgroundAlpha();
    341                 if (initialAlpha != finalAlpha) {
    342                     LauncherViewPropertyAnimator alphaAnim =
    343                             new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
    344                     alphaAnim.alpha(finalAlpha)
    345                             .setDuration(duration)
    346                             .setInterpolator(mZoomInInterpolator);
    347                     mStateAnimator.play(alphaAnim);
    348                 }
    349                 if (oldBackgroundAlpha != 0 || finalBackgroundAlpha != 0) {
    350                     ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
    351                             oldBackgroundAlpha, finalBackgroundAlpha);
    352                     bgAnim.setInterpolator(mZoomInInterpolator);
    353                     bgAnim.setDuration(duration);
    354                     mStateAnimator.play(bgAnim);
    355                 }
    356             } else {
    357                 cl.setBackgroundAlpha(finalBackgroundAlpha);
    358                 cl.setShortcutAndWidgetAlpha(finalAlpha);
    359             }
    360 
    361             if (Workspace.isQsbContainerPage(i)) {
    362                 if (animated) {
    363                     Animator anim = mWorkspace.mQsbAlphaController
    364                             .animateAlphaAtIndex(finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
    365                     anim.setDuration(duration);
    366                     anim.setInterpolator(mZoomInInterpolator);
    367                     mStateAnimator.play(anim);
    368                 } else {
    369                     mWorkspace.mQsbAlphaController.setAlphaAtIndex(
    370                             finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
    371                 }
    372             }
    373         }
    374 
    375         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
    376 
    377         final View qsbContainer = mLauncher.getQsbContainer();
    378 
    379         Animator qsbAlphaAnimation = mWorkspace.mQsbAlphaController
    380                 .animateAlphaAtIndex(finalQsbAlpha, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE);
    381 
    382         if (animated) {
    383             LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
    384             scale.scaleX(mNewScale)
    385                     .scaleY(mNewScale)
    386                     .translationY(finalWorkspaceTranslationY)
    387                     .setDuration(duration)
    388                     .setInterpolator(mZoomInInterpolator);
    389             mStateAnimator.play(scale);
    390             Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
    391 
    392             LauncherViewPropertyAnimator overviewPanelAlpha =
    393                     new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
    394             overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
    395                     accessibilityEnabled));
    396 
    397             // For animation optimization, we may need to provide the Launcher transition
    398             // with a set of views on which to force build and manage layers in certain scenarios.
    399             layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
    400             layerViews.put(qsbContainer, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
    401             layerViews.put(mLauncher.getHotseat(),
    402                     LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
    403             layerViews.put(mWorkspace.getPageIndicator(),
    404                     LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
    405 
    406             if (states.workspaceToOverview) {
    407                 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
    408                 overviewPanelAlpha.setInterpolator(null);
    409             } else if (states.overviewToWorkspace) {
    410                 hotseatAlpha.setInterpolator(null);
    411                 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
    412             }
    413 
    414             overviewPanelAlpha.setDuration(duration);
    415             hotseatAlpha.setDuration(duration);
    416             qsbAlphaAnimation.setDuration(duration);
    417 
    418             mStateAnimator.play(overviewPanelAlpha);
    419             mStateAnimator.play(hotseatAlpha);
    420             mStateAnimator.play(qsbAlphaAnimation);
    421             mStateAnimator.addListener(new AnimatorListenerAdapter() {
    422                 boolean canceled = false;
    423                 @Override
    424                 public void onAnimationCancel(Animator animation) {
    425                     canceled = true;
    426                 }
    427 
    428                 @Override
    429                 public void onAnimationEnd(Animator animation) {
    430                     mStateAnimator = null;
    431                     if (canceled) return;
    432                     if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
    433                         overviewPanel.getChildAt(0).performAccessibilityAction(
    434                                 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
    435                     }
    436                 }
    437             });
    438         } else {
    439             overviewPanel.setAlpha(finalOverviewPanelAlpha);
    440             AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
    441 
    442             qsbAlphaAnimation.end();
    443             mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
    444             mWorkspace.updateCustomContentVisibility();
    445             mWorkspace.setScaleX(mNewScale);
    446             mWorkspace.setScaleY(mNewScale);
    447             mWorkspace.setTranslationY(finalWorkspaceTranslationY);
    448 
    449             if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
    450                 overviewPanel.getChildAt(0).performAccessibilityAction(
    451                         AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
    452             }
    453         }
    454     }
    455 
    456     /**
    457      * Animates the background scrim. Add to the state animator to prevent jankiness.
    458      *
    459      * @param states the current and final workspace states
    460      * @param animated whether or not to set the background alpha immediately
    461      * @duration duration of the animation
    462      */
    463     private void animateBackgroundGradient(TransitionStates states,
    464             boolean animated, int duration) {
    465 
    466         final DragLayer dragLayer = mLauncher.getDragLayer();
    467         final float startAlpha = dragLayer.getBackgroundAlpha();
    468         float finalAlpha = states.stateIsNormal || states.stateIsNormalHidden ?
    469                 0 : mWorkspaceScrimAlpha;
    470 
    471         if (finalAlpha != startAlpha) {
    472             if (animated) {
    473                 // These properties refer to the background protection gradient used for AllApps
    474                 // and Widget tray.
    475                 ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
    476                 bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    477                     @Override
    478                     public void onAnimationUpdate(ValueAnimator animation) {
    479                         dragLayer.setBackgroundAlpha(
    480                                 ((Float)animation.getAnimatedValue()).floatValue());
    481                     }
    482                 });
    483                 bgFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
    484                 bgFadeOutAnimation.setDuration(duration);
    485                 mStateAnimator.play(bgFadeOutAnimation);
    486             } else {
    487                 dragLayer.setBackgroundAlpha(finalAlpha);
    488             }
    489         }
    490     }
    491 
    492     /**
    493      * Cancels the current animation.
    494      */
    495     private void cancelAnimation() {
    496         if (mStateAnimator != null) {
    497             mStateAnimator.setDuration(0);
    498             mStateAnimator.cancel();
    499         }
    500         mStateAnimator = null;
    501     }
    502 }