• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AlphaUpdateListener(View v, boolean accessibilityEnabled)49     public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
50         mView = v;
51         mAccessibilityEnabled = accessibilityEnabled;
52     }
53 
54     @Override
onAnimationUpdate(ValueAnimator arg0)55     public void onAnimationUpdate(ValueAnimator arg0) {
56         updateVisibility(mView, mAccessibilityEnabled);
57     }
58 
updateVisibility(View view, boolean accessibilityEnabled)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
onAnimationCancel(Animator animation)72     public void onAnimationCancel(Animator animation) {
73         mCanceled = true;
74     }
75 
76     @Override
onAnimationEnd(Animator arg0)77     public void onAnimationEnd(Animator arg0) {
78         if (mCanceled) return;
79         updateVisibility(mView, mAccessibilityEnabled);
80     }
81 
82     @Override
onAnimationStart(Animator arg0)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 
ZInterpolator(float foc)98     public ZInterpolator(float foc) {
99         focalLength = foc;
100     }
101 
getInterpolation(float input)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;
InverseZInterpolator(float foc)113     public InverseZInterpolator(float foc) {
114         zInterpolator = new ZInterpolator(foc);
115     }
getInterpolation(float input)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 
getInterpolation(float input)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 
TransitionStates(final Workspace.State fromState, final Workspace.State toState)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 
WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace)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 
snapToPageFromOverView(int whichPage)224     public void snapToPageFromOverView(int whichPage) {
225         mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator);
226     }
227 
getAnimationToState(Workspace.State fromState, Workspace.State toState, boolean animated, HashMap<View, Integer> layerViews)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 
getFinalScale()241     public float getFinalScale() {
242         return mNewScale;
243     }
244 
245     /**
246      * Returns the proper animation duration for a transition.
247      */
getAnimationDuration(TransitionStates states)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      */
animateWorkspace(final TransitionStates states, final boolean animated, final int duration, final HashMap<View, Integer> layerViews, final boolean accessibilityEnabled)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      */
animateBackgroundGradient(TransitionStates states, boolean animated, int duration)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      */
cancelAnimation()495     private void cancelAnimation() {
496         if (mStateAnimator != null) {
497             mStateAnimator.setDuration(0);
498             mStateAnimator.cancel();
499         }
500         mStateAnimator = null;
501     }
502 }