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