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