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.anim.AnimationLayerSet;
     34 import com.android.launcher3.anim.PropertyListBuilder;
     35 import com.android.launcher3.config.FeatureFlags;
     36 import com.android.launcher3.dragndrop.DragLayer;
     37 import com.android.launcher3.util.Thunk;
     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, AnimationLayerSet 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, AnimationLayerSet layerViews, final boolean accessibilityEnabled) {
    266         // Cancel existing workspace animations and create a new animator set if requested
    267         cancelAnimation();
    268         if (animated) {
    269             mStateAnimator = LauncherAnimUtils.createAnimatorSet();
    270         }
    271 
    272         // Update the workspace state
    273         float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
    274                 1.0f : 0f;
    275         float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded ||
    276                 states.stateIsNormalHidden) ? 1f : 0f;
    277         float finalQsbAlpha = (states.stateIsNormal || states.stateIsNormalHidden) ? 1f : 0f;
    278 
    279         float finalWorkspaceTranslationY = 0;
    280         if (states.stateIsOverview || states.stateIsOverviewHidden) {
    281             finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
    282         } else if (states.stateIsSpringLoaded) {
    283             finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
    284         }
    285 
    286         final int childCount = mWorkspace.getChildCount();
    287         final int customPageCount = mWorkspace.numCustomPages();
    288 
    289         mNewScale = 1.0f;
    290 
    291         if (states.oldStateIsOverview) {
    292             mWorkspace.disableFreeScroll();
    293         } else if (states.stateIsOverview) {
    294             mWorkspace.enableFreeScroll();
    295         }
    296 
    297         if (!states.stateIsNormal) {
    298             if (states.stateIsSpringLoaded) {
    299                 mNewScale = mSpringLoadedShrinkFactor;
    300             } else if (states.stateIsOverview || states.stateIsOverviewHidden) {
    301                 mNewScale = mOverviewModeShrinkFactor;
    302             }
    303         }
    304 
    305         int toPage = mWorkspace.getPageNearestToCenterOfScreen();
    306         // TODO: Animate the celllayout alpha instead of the pages.
    307         for (int i = 0; i < childCount; i++) {
    308             final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
    309             float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
    310             float finalAlpha;
    311             if (states.stateIsOverviewHidden) {
    312                 finalAlpha = 0f;
    313             } else if(states.stateIsNormalHidden) {
    314                 finalAlpha = (i == mWorkspace.getNextPage()) ? 1 : 0;
    315             } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
    316                 finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
    317             } else {
    318                 finalAlpha = 1f;
    319             }
    320 
    321             // If we are animating to/from the small state, then hide the side pages and fade the
    322             // current page in
    323             if (!FeatureFlags.NO_ALL_APPS_ICON && !mWorkspace.isSwitchingState()) {
    324                 if (states.workspaceToAllApps || states.allAppsToWorkspace) {
    325                     boolean isCurrentPage = (i == toPage);
    326                     if (states.allAppsToWorkspace && isCurrentPage) {
    327                         initialAlpha = 0f;
    328                     } else if (!isCurrentPage) {
    329                         initialAlpha = finalAlpha = 0f;
    330                     }
    331                     cl.setShortcutAndWidgetAlpha(initialAlpha);
    332                 }
    333             }
    334 
    335             if (animated) {
    336                 float oldBackgroundAlpha = cl.getBackgroundAlpha();
    337                 if (initialAlpha != finalAlpha) {
    338                     Animator alphaAnim = ObjectAnimator.ofFloat(
    339                             cl.getShortcutsAndWidgets(), View.ALPHA, finalAlpha);
    340                     alphaAnim.setDuration(duration)
    341                             .setInterpolator(mZoomInInterpolator);
    342                     mStateAnimator.play(alphaAnim);
    343                 }
    344                 if (oldBackgroundAlpha != 0 || finalBackgroundAlpha != 0) {
    345                     ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
    346                             oldBackgroundAlpha, finalBackgroundAlpha);
    347                     bgAnim.setInterpolator(mZoomInInterpolator);
    348                     bgAnim.setDuration(duration);
    349                     mStateAnimator.play(bgAnim);
    350                 }
    351             } else {
    352                 cl.setBackgroundAlpha(finalBackgroundAlpha);
    353                 cl.setShortcutAndWidgetAlpha(finalAlpha);
    354             }
    355         }
    356 
    357         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
    358 
    359         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
    360         if (animated) {
    361             // This is true when transitioning between:
    362             // - Overview <-> Workspace
    363             // - Overview <-> Widget Tray
    364             if (finalOverviewPanelAlpha != overviewPanel.getAlpha()) {
    365                 Animator overviewPanelAlpha = ObjectAnimator.ofFloat(
    366                         overviewPanel, View.ALPHA, finalOverviewPanelAlpha);
    367                 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
    368                         accessibilityEnabled));
    369                 layerViews.addView(overviewPanel);
    370 
    371                 if (states.overviewToWorkspace) {
    372                     overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
    373                 } else if (states.workspaceToOverview) {
    374                     overviewPanelAlpha.setInterpolator(null);
    375                 }
    376 
    377                 overviewPanelAlpha.setDuration(duration);
    378                 mStateAnimator.play(overviewPanelAlpha);
    379             }
    380 
    381             Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace,
    382                     new PropertyListBuilder().scale(mNewScale)
    383                             .translationY(finalWorkspaceTranslationY).build())
    384                     .setDuration(duration);
    385             scale.setInterpolator(mZoomInInterpolator);
    386             mStateAnimator.play(scale);
    387 
    388             // For animation optimization, we may need to provide the Launcher transition
    389             // with a set of views on which to force build and manage layers in certain scenarios.
    390             layerViews.addView(mLauncher.getHotseat());
    391             layerViews.addView(mWorkspace.getPageIndicator());
    392 
    393             Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
    394             if (states.workspaceToOverview) {
    395                 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
    396             } else if (states.overviewToWorkspace) {
    397                 hotseatAlpha.setInterpolator(null);
    398             }
    399             hotseatAlpha.setDuration(duration);
    400             mStateAnimator.play(hotseatAlpha);
    401             mStateAnimator.addListener(new AnimatorListenerAdapter() {
    402                 boolean canceled = false;
    403                 @Override
    404                 public void onAnimationCancel(Animator animation) {
    405                     canceled = true;
    406                 }
    407 
    408                 @Override
    409                 public void onAnimationStart(Animator animation) {
    410                     mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
    411                 }
    412 
    413                 @Override
    414                 public void onAnimationEnd(Animator animation) {
    415                     mStateAnimator = null;
    416                     if (canceled) return;
    417                     if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
    418                         overviewPanel.getChildAt(0).performAccessibilityAction(
    419                                 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
    420                     }
    421                 }
    422             });
    423         } else {
    424             overviewPanel.setAlpha(finalOverviewPanelAlpha);
    425             AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
    426             mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
    427 
    428             mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
    429             mWorkspace.updateCustomContentVisibility();
    430             mWorkspace.setScaleX(mNewScale);
    431             mWorkspace.setScaleY(mNewScale);
    432             mWorkspace.setTranslationY(finalWorkspaceTranslationY);
    433 
    434             if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
    435                 overviewPanel.getChildAt(0).performAccessibilityAction(
    436                         AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
    437             }
    438         }
    439     }
    440 
    441     /**
    442      * Animates the background scrim. Add to the state animator to prevent jankiness.
    443      *
    444      * @param states the current and final workspace states
    445      * @param animated whether or not to set the background alpha immediately
    446      * @duration duration of the animation
    447      */
    448     private void animateBackgroundGradient(TransitionStates states,
    449             boolean animated, int duration) {
    450 
    451         final DragLayer dragLayer = mLauncher.getDragLayer();
    452         final float startAlpha = dragLayer.getBackgroundAlpha();
    453         float finalAlpha = states.stateIsNormal || states.stateIsNormalHidden ?
    454                 0 : mWorkspaceScrimAlpha;
    455 
    456         if (finalAlpha != startAlpha) {
    457             if (animated) {
    458                 // These properties refer to the background protection gradient used for AllApps
    459                 // and Widget tray.
    460                 ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
    461                 bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    462                     @Override
    463                     public void onAnimationUpdate(ValueAnimator animation) {
    464                         dragLayer.setBackgroundAlpha(
    465                                 ((Float)animation.getAnimatedValue()).floatValue());
    466                     }
    467                 });
    468                 bgFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
    469                 bgFadeOutAnimation.setDuration(duration);
    470                 mStateAnimator.play(bgFadeOutAnimation);
    471             } else {
    472                 dragLayer.setBackgroundAlpha(finalAlpha);
    473             }
    474         }
    475     }
    476 
    477     /**
    478      * Cancels the current animation.
    479      */
    480     private void cancelAnimation() {
    481         if (mStateAnimator != null) {
    482             mStateAnimator.setDuration(0);
    483             mStateAnimator.cancel();
    484         }
    485         mStateAnimator = null;
    486     }
    487 }