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