• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.util.Log;
24 import android.view.View;
25 import android.view.animation.LinearInterpolator;
26 
27 import static com.android.launcher3.Workspace.State.NORMAL;
28 import static com.android.launcher3.Workspace.State.OVERVIEW;
29 
30 /**
31  * Manages the animations that play as the user pinches to/from overview mode.
32  *
33  *  It will look like this pinching in:
34  * - Workspace scales down
35  * - At some threshold 1, hotseat and QSB fade out (full animation)
36  * - At a later threshold 2, panel buttons fade in and scrim fades in
37  * - At a final threshold 3, snap to overview
38  *
39  * Pinching out:
40  * - Workspace scales up
41  * - At threshold 1, panel buttons fade out
42  * - At threshold 2, hotseat and QSB fade in and scrim fades out
43  * - At threshold 3, snap to workspace
44  *
45  * @see PinchToOverviewListener
46  * @see PinchThresholdManager
47  */
48 public class PinchAnimationManager {
49     private static final String TAG = "PinchAnimationManager";
50 
51     private static final int THRESHOLD_ANIM_DURATION = 150;
52     private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
53 
54     private static final int INDEX_HOTSEAT = 0;
55     private static final int INDEX_QSB = 1;
56     private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 2;
57     private static final int INDEX_SCRIM = 3;
58 
59     private final Animator[] mAnimators = new Animator[4];
60 
61     private final int[] mVisiblePageRange = new int[2];
62     private Launcher mLauncher;
63     private Workspace mWorkspace;
64 
65     private float mOverviewScale;
66     private float mOverviewTranslationY;
67     private int mNormalOverviewTransitionDuration;
68     private boolean mIsAnimating;
69 
PinchAnimationManager(Launcher launcher)70     public PinchAnimationManager(Launcher launcher) {
71         mLauncher = launcher;
72         mWorkspace = launcher.mWorkspace;
73 
74         mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
75         mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
76         mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
77                 .mOverviewTransitionTime;
78     }
79 
getNormalOverviewTransitionDuration()80     public int getNormalOverviewTransitionDuration() {
81         return mNormalOverviewTransitionDuration;
82     }
83 
84     /**
85      * Interpolate from {@param currentProgress} to {@param toProgress}, calling
86      * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
87      * the default overview transition duration is used.
88      */
animateToProgress(float currentProgress, float toProgress, int duration, final PinchThresholdManager thresholdManager)89     public void animateToProgress(float currentProgress, float toProgress, int duration,
90             final PinchThresholdManager thresholdManager) {
91         if (duration == -1) {
92             duration = mNormalOverviewTransitionDuration;
93         }
94         ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
95         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
96                 @Override
97                 public void onAnimationUpdate(ValueAnimator animation) {
98                     float pinchProgress = (Float) animation.getAnimatedValue();
99                     setAnimationProgress(pinchProgress);
100                     thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
101                             PinchAnimationManager.this);
102                 }
103             }
104         );
105         animator.addListener(new AnimatorListenerAdapter() {
106             @Override
107             public void onAnimationEnd(Animator animation) {
108                 mIsAnimating = false;
109                 thresholdManager.reset();
110                 mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
111             }
112         });
113         animator.setDuration(duration).start();
114         mIsAnimating = true;
115     }
116 
isAnimating()117     public boolean isAnimating() {
118         return mIsAnimating;
119     }
120 
121     /**
122      * Animates to the specified progress. This should be called repeatedly throughout the pinch
123      * gesture to run animations that interpolate throughout the gesture.
124      * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
125      */
setAnimationProgress(float interpolatedProgress)126     public void setAnimationProgress(float interpolatedProgress) {
127         float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
128         float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
129         mWorkspace.setScaleX(interpolatedScale);
130         mWorkspace.setScaleY(interpolatedScale);
131         mWorkspace.setTranslationY(interpolatedTranslationY);
132         setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
133     }
134 
135     /**
136      * Animates certain properties based on which threshold was passed, and in what direction. The
137      * starting state must also be taken into account because the thresholds mean different things
138      * when going from workspace to overview and vice versa.
139      * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
140      *                  {@link PinchThresholdManager#THRESHOLD_TWO}, or
141      *                  {@link PinchThresholdManager#THRESHOLD_THREE}
142      * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
143      * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
144      *                     Note that this doesn't have to be the opposite of startState;
145      */
animateThreshold(float threshold, Workspace.State startState, Workspace.State goingTowards)146     public void animateThreshold(float threshold, Workspace.State startState,
147             Workspace.State goingTowards) {
148         if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
149             if (startState == OVERVIEW) {
150                 animateOverviewPanelButtons(goingTowards == OVERVIEW);
151             } else if (startState == NORMAL) {
152                 animateHotseatAndQsb(goingTowards == NORMAL);
153             }
154         } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
155             if (startState == OVERVIEW) {
156                 animateHotseatAndQsb(goingTowards == NORMAL);
157                 animateScrim(goingTowards == OVERVIEW);
158             } else if (startState == NORMAL) {
159                 animateOverviewPanelButtons(goingTowards == OVERVIEW);
160                 animateScrim(goingTowards == OVERVIEW);
161             }
162         } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
163             // Passing threshold 3 ends the pinch and snaps to the new state.
164             if (startState == OVERVIEW && goingTowards == NORMAL) {
165                 mLauncher.showWorkspace(true);
166                 mWorkspace.snapToPage(mWorkspace.getCurrentPage());
167             } else if (startState == NORMAL && goingTowards == OVERVIEW) {
168                 mLauncher.showOverviewMode(true);
169             }
170         } else {
171             Log.e(TAG, "Received unknown threshold to animate: " + threshold);
172         }
173     }
174 
setOverviewPanelsAlpha(float alpha, int duration)175     private void setOverviewPanelsAlpha(float alpha, int duration) {
176         mWorkspace.getVisiblePages(mVisiblePageRange);
177         for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
178             View page = mWorkspace.getPageAt(i);
179             if (!mWorkspace.shouldDrawChild(page)) {
180                 continue;
181             }
182             if (duration == 0) {
183                 ((CellLayout) page).setBackgroundAlpha(alpha);
184             } else {
185                 ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
186                         .setDuration(duration).start();
187             }
188         }
189     }
190 
animateHotseatAndQsb(boolean show)191     private void animateHotseatAndQsb(boolean show) {
192         startAnimator(INDEX_HOTSEAT,
193                 mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION);
194         startAnimator(INDEX_QSB, mWorkspace.mQsbAlphaController.animateAlphaAtIndex(
195                 show ? 1 : 0, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE), THRESHOLD_ANIM_DURATION);
196     }
197 
animateOverviewPanelButtons(boolean show)198     private void animateOverviewPanelButtons(boolean show) {
199         animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
200     }
201 
animateScrim(boolean show)202     private void animateScrim(boolean show) {
203         float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
204         startAnimator(INDEX_SCRIM,
205                 ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue),
206                 mNormalOverviewTransitionDuration);
207     }
208 
animateShowHideView(int index, final View view, boolean show)209     private void animateShowHideView(int index, final View view, boolean show) {
210         Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer();
211         if (show) {
212             view.setVisibility(View.VISIBLE);
213         } else {
214             animator.addListener(new AnimatorListenerAdapter() {
215                 @Override
216                 public void onAnimationEnd(Animator animation) {
217                     view.setVisibility(View.INVISIBLE);
218                 }
219             });
220         }
221         startAnimator(index, animator, THRESHOLD_ANIM_DURATION);
222     }
223 
startAnimator(int index, Animator animator, long duration)224     private void startAnimator(int index, Animator animator, long duration) {
225         if (mAnimators[index] != null) {
226             mAnimators[index].removeAllListeners();
227             mAnimators[index].cancel();
228         }
229         mAnimators[index] = animator;
230         mAnimators[index].setInterpolator(INTERPOLATOR);
231         mAnimators[index].setDuration(duration).start();
232     }
233 }
234