blob: 3302cb65275e58899ec4d1d874a74a2b3312ac1e [file] [log] [blame]
Tony Wickhamdadb3042016-02-24 11:07:00 -08001package com.android.launcher3;
2
3import android.animation.Animator;
4import android.animation.AnimatorListenerAdapter;
5import android.animation.ObjectAnimator;
6import android.animation.ValueAnimator;
7import android.util.Log;
8import android.view.View;
9
10import static com.android.launcher3.Workspace.State.NORMAL;
11import static com.android.launcher3.Workspace.State.OVERVIEW;
12
13/**
14 * Manages the animations that play as the user pinches to/from overview mode.
15 *
16 * It will look like this pinching in:
17 * - Workspace scales down
18 * - At some threshold 1, hotseat and QSB fade out (full animation)
19 * - At a later threshold 2, panel buttons fade in and scrim fades in
20 * - At a final threshold 3, snap to overview
21 *
22 * Pinching out:
23 * - Workspace scales up
24 * - At threshold 1, panel buttons fade out
25 * - At threshold 2, hotseat and QSB fade in and scrim fades out
26 * - At threshold 3, snap to workspace
27 *
28 * @see PinchToOverviewListener
29 * @see PinchThresholdManager
30 */
31public class PinchAnimationManager {
32 private static final String TAG = "PinchAnimationManager";
33
34 private static final int THRESHOLD_ANIM_DURATION = 150;
35
36 private Launcher mLauncher;
37 private Workspace mWorkspace;
38
39 private float mOverviewScale;
40 private float mOverviewTranslationY;
41 private int mNormalOverviewTransitionDuration;
42 private final int[] mVisiblePageRange = new int[2];
43 private boolean mIsAnimating;
44
45 // Animators
46 private Animator mShowPageIndicatorAnimator;
47 private Animator mShowHotseatAnimator;
48 private Animator mShowOverviewPanelButtonsAnimator;
49 private Animator mShowScrimAnimator;
50 private Animator mHidePageIndicatorAnimator;
51 private Animator mHideHotseatAnimator;
52 private Animator mHideOverviewPanelButtonsAnimator;
53 private Animator mHideScrimAnimator;
54
55 public PinchAnimationManager(Launcher launcher) {
56 mLauncher = launcher;
57 mWorkspace = launcher.mWorkspace;
58
59 mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
60 mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
61 mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
62 .mOverviewTransitionTime;
63
64 initializeAnimators();
65 }
66
67 private void initializeAnimators() {
68 mShowPageIndicatorAnimator = new LauncherViewPropertyAnimator(
69 mWorkspace.getPageIndicator()).alpha(1f).withLayer();
70 mShowPageIndicatorAnimator.setInterpolator(null);
71
72 mShowHotseatAnimator = new LauncherViewPropertyAnimator(mLauncher.getHotseat())
73 .alpha(1f).withLayer();
74 mShowHotseatAnimator.setInterpolator(null);
75
76 mShowOverviewPanelButtonsAnimator = new LauncherViewPropertyAnimator(
77 mLauncher.getOverviewPanel()).alpha(1f).withLayer();
78 mShowOverviewPanelButtonsAnimator.setInterpolator(null);
79
80 mShowScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha",
81 mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha);
82 mShowScrimAnimator.setInterpolator(null);
83
84 mHidePageIndicatorAnimator = new LauncherViewPropertyAnimator(
85 mWorkspace.getPageIndicator()).alpha(0f).withLayer();
86 mHidePageIndicatorAnimator.setInterpolator(null);
87 mHidePageIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
88 @Override
89 public void onAnimationEnd(Animator animation) {
90 if (mWorkspace.getPageIndicator() != null) {
91 mWorkspace.getPageIndicator().setVisibility(View.INVISIBLE);
92 }
93 }
94 });
95
96 mHideHotseatAnimator = new LauncherViewPropertyAnimator(mLauncher.getHotseat())
97 .alpha(0f).withLayer();
98 mHideHotseatAnimator.setInterpolator(null);
99 mHideHotseatAnimator.addListener(new AnimatorListenerAdapter() {
100 @Override
101 public void onAnimationEnd(Animator animation) {
102 mLauncher.getHotseat().setVisibility(View.INVISIBLE);
103 }
104 });
105
106 mHideOverviewPanelButtonsAnimator = new LauncherViewPropertyAnimator(
107 mLauncher.getOverviewPanel()).alpha(0f).withLayer();
108 mHideOverviewPanelButtonsAnimator.setInterpolator(null);
109 mHideOverviewPanelButtonsAnimator.addListener(new AnimatorListenerAdapter() {
110 @Override
111 public void onAnimationEnd(Animator animation) {
112 mLauncher.getOverviewPanel().setVisibility(View.INVISIBLE);
113 }
114 });
115
116 mHideScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", 0f);
117 mHideScrimAnimator.setInterpolator(null);
118 }
119
120 public int getNormalOverviewTransitionDuration() {
121 return mNormalOverviewTransitionDuration;
122 }
123
124 /**
125 * Interpolate from {@param currentProgress} to {@param toProgress}, calling
126 * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
127 * the default overview transition duration is used.
128 */
129 public void animateToProgress(float currentProgress, float toProgress, int duration,
130 final PinchThresholdManager thresholdManager) {
131 if (duration == -1) {
132 duration = mNormalOverviewTransitionDuration;
133 }
134 ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
135 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
136 @Override
137 public void onAnimationUpdate(ValueAnimator animation) {
138 float pinchProgress = (Float) animation.getAnimatedValue();
139 setAnimationProgress(pinchProgress);
140 thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
141 PinchAnimationManager.this);
142 }
143 }
144 );
145 animator.addListener(new AnimatorListenerAdapter() {
146 @Override
147 public void onAnimationEnd(Animator animation) {
148 mIsAnimating = false;
149 thresholdManager.reset();
150 }
151 });
152 animator.setDuration(duration).start();
153 mIsAnimating = true;
154 }
155
156 public boolean isAnimating() {
157 return mIsAnimating;
158 }
159
160 /**
161 * Animates to the specified progress. This should be called repeatedly throughout the pinch
162 * gesture to run animations that interpolate throughout the gesture.
163 * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
164 */
165 public void setAnimationProgress(float interpolatedProgress) {
166 float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
167 float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
168 mWorkspace.setScaleX(interpolatedScale);
169 mWorkspace.setScaleY(interpolatedScale);
170 mWorkspace.setTranslationY(interpolatedTranslationY);
171 setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
172
173 // Make sure adjacent pages, except custom content page, are visible while scaling.
174 mWorkspace.setCustomContentVisibility(View.INVISIBLE);
175 mWorkspace.invalidate();
176 }
177
178 /**
179 * Animates certain properties based on which threshold was passed, and in what direction. The
180 * starting state must also be taken into account because the thresholds mean different things
181 * when going from workspace to overview and vice versa.
182 * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
183 * {@link PinchThresholdManager#THRESHOLD_TWO}, or
184 * {@link PinchThresholdManager#THRESHOLD_THREE}
185 * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
186 * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
187 * Note that this doesn't have to be the opposite of startState;
188 */
189 public void animateThreshold(float threshold, Workspace.State startState,
190 Workspace.State goingTowards) {
191 if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
192 if (startState == OVERVIEW) {
193 animateOverviewPanelButtons(goingTowards == OVERVIEW);
194 } else if (startState == NORMAL) {
195 animateHotseatAndPageIndicator(goingTowards == NORMAL);
196 animateQsb(goingTowards == NORMAL);
197 }
198 } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
199 if (startState == OVERVIEW) {
200 animateHotseatAndPageIndicator(goingTowards == NORMAL);
201 animateQsb(goingTowards == NORMAL);
202 animateScrim(goingTowards == OVERVIEW);
203 } else if (startState == NORMAL) {
204 animateOverviewPanelButtons(goingTowards == OVERVIEW);
205 animateScrim(goingTowards == OVERVIEW);
206 }
207 } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
208 // Passing threshold 3 ends the pinch and snaps to the new state.
209 if (startState == OVERVIEW && goingTowards == NORMAL) {
210 mLauncher.showWorkspace(true);
211 mWorkspace.snapToPage(mWorkspace.getPageNearestToCenterOfScreen());
212 } else if (startState == NORMAL && goingTowards == OVERVIEW) {
213 mLauncher.showOverviewMode(true);
214 }
215 } else {
216 Log.e(TAG, "Received unknown threshold to animate: " + threshold);
217 }
218 }
219
220 private void setOverviewPanelsAlpha(float alpha, int duration) {
221 mWorkspace.getVisiblePages(mVisiblePageRange);
222 for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
223 View page = mWorkspace.getPageAt(i);
224 if (!mWorkspace.shouldDrawChild(page)) {
225 continue;
226 }
227 if (duration == 0) {
228 ((CellLayout) page).setBackgroundAlpha(alpha);
229 } else {
230 ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
231 .setDuration(duration).start();
232 }
233 }
234 }
235
236 private void animateHotseatAndPageIndicator(boolean show) {
237 if (show) {
238 mLauncher.getHotseat().setVisibility(View.VISIBLE);
239 mShowHotseatAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
240 if (mWorkspace.getPageIndicator() != null) {
241 // There aren't page indicators in landscape mode on phones, hence the null check.
242 mWorkspace.getPageIndicator().setVisibility(View.VISIBLE);
243 mShowPageIndicatorAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
244 }
245 } else {
246 mHideHotseatAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
247 if (mWorkspace.getPageIndicator() != null) {
248 // There aren't page indicators in landscape mode on phones, hence the null check.
249 mHidePageIndicatorAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
250 }
251 }
252 }
253
254 private void animateQsb(boolean show) {
255 SearchDropTargetBar.State searchBarState = show ? SearchDropTargetBar.State.SEARCH_BAR
256 : SearchDropTargetBar.State.INVISIBLE;
257 mLauncher.getSearchDropTargetBar().animateToState(searchBarState, THRESHOLD_ANIM_DURATION);
258 }
259
260 private void animateOverviewPanelButtons(boolean show) {
261 if (show) {
262 mLauncher.getOverviewPanel().setVisibility(View.VISIBLE);
263 mShowOverviewPanelButtonsAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
264 } else {
265 mHideOverviewPanelButtonsAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
266 }
267 }
268
269 private void animateScrim(boolean show) {
270 // We reninitialize the animators here so that they have the correct start values.
271 if (show) {
272 mShowScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha",
273 mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha);
274 mShowScrimAnimator.setInterpolator(null);
275 mShowScrimAnimator.setDuration(mNormalOverviewTransitionDuration).start();
276 } else {
277 mHideScrimAnimator.setupStartValues();
278 mHideScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha",
279 0f);
280 mHideScrimAnimator.setInterpolator(null);
281 mHideScrimAnimator.setDuration(mNormalOverviewTransitionDuration).start();
282 mHideScrimAnimator.setDuration(mNormalOverviewTransitionDuration).start();
283 }
284 }
285}