blob: e4f8bc71b9598a704d1970a9fac57d76f87784fa [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;
Sunny Goyaled268c22016-03-24 15:27:42 -07009import android.view.animation.LinearInterpolator;
Tony Wickhamdadb3042016-02-24 11:07:00 -080010
11import static com.android.launcher3.Workspace.State.NORMAL;
12import static com.android.launcher3.Workspace.State.OVERVIEW;
13
14/**
15 * Manages the animations that play as the user pinches to/from overview mode.
16 *
17 * It will look like this pinching in:
18 * - Workspace scales down
19 * - At some threshold 1, hotseat and QSB fade out (full animation)
20 * - At a later threshold 2, panel buttons fade in and scrim fades in
21 * - At a final threshold 3, snap to overview
22 *
23 * Pinching out:
24 * - Workspace scales up
25 * - At threshold 1, panel buttons fade out
26 * - At threshold 2, hotseat and QSB fade in and scrim fades out
27 * - At threshold 3, snap to workspace
28 *
29 * @see PinchToOverviewListener
30 * @see PinchThresholdManager
31 */
32public class PinchAnimationManager {
33 private static final String TAG = "PinchAnimationManager";
34
35 private static final int THRESHOLD_ANIM_DURATION = 150;
Sunny Goyaled268c22016-03-24 15:27:42 -070036 private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
Tony Wickhamdadb3042016-02-24 11:07:00 -080037
Sunny Goyaled268c22016-03-24 15:27:42 -070038 private static int INDEX_PAGE_INDICATOR = 0;
39 private static int INDEX_HOTSEAT = 1;
40 private static int INDEX_OVERVIEW_PANEL_BUTTONS = 2;
41 private static int INDEX_SCRIM = 3;
42
43 private final Animator[] mAnimators = new Animator[4];
44
45 private final int[] mVisiblePageRange = new int[2];
Tony Wickhamdadb3042016-02-24 11:07:00 -080046 private Launcher mLauncher;
47 private Workspace mWorkspace;
48
49 private float mOverviewScale;
50 private float mOverviewTranslationY;
51 private int mNormalOverviewTransitionDuration;
Tony Wickhamdadb3042016-02-24 11:07:00 -080052 private boolean mIsAnimating;
53
Tony Wickhamdadb3042016-02-24 11:07:00 -080054 public PinchAnimationManager(Launcher launcher) {
55 mLauncher = launcher;
56 mWorkspace = launcher.mWorkspace;
57
58 mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
59 mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
60 mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
61 .mOverviewTransitionTime;
Tony Wickhamdadb3042016-02-24 11:07:00 -080062 }
63
64 public int getNormalOverviewTransitionDuration() {
65 return mNormalOverviewTransitionDuration;
66 }
67
68 /**
69 * Interpolate from {@param currentProgress} to {@param toProgress}, calling
70 * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
71 * the default overview transition duration is used.
72 */
73 public void animateToProgress(float currentProgress, float toProgress, int duration,
74 final PinchThresholdManager thresholdManager) {
75 if (duration == -1) {
76 duration = mNormalOverviewTransitionDuration;
77 }
78 ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
79 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
80 @Override
81 public void onAnimationUpdate(ValueAnimator animation) {
82 float pinchProgress = (Float) animation.getAnimatedValue();
83 setAnimationProgress(pinchProgress);
84 thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
85 PinchAnimationManager.this);
86 }
87 }
88 );
89 animator.addListener(new AnimatorListenerAdapter() {
90 @Override
91 public void onAnimationEnd(Animator animation) {
92 mIsAnimating = false;
93 thresholdManager.reset();
94 }
95 });
96 animator.setDuration(duration).start();
97 mIsAnimating = true;
98 }
99
100 public boolean isAnimating() {
101 return mIsAnimating;
102 }
103
104 /**
105 * Animates to the specified progress. This should be called repeatedly throughout the pinch
106 * gesture to run animations that interpolate throughout the gesture.
107 * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
108 */
109 public void setAnimationProgress(float interpolatedProgress) {
110 float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
111 float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
112 mWorkspace.setScaleX(interpolatedScale);
113 mWorkspace.setScaleY(interpolatedScale);
114 mWorkspace.setTranslationY(interpolatedTranslationY);
115 setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
116
117 // Make sure adjacent pages, except custom content page, are visible while scaling.
118 mWorkspace.setCustomContentVisibility(View.INVISIBLE);
119 mWorkspace.invalidate();
120 }
121
122 /**
123 * Animates certain properties based on which threshold was passed, and in what direction. The
124 * starting state must also be taken into account because the thresholds mean different things
125 * when going from workspace to overview and vice versa.
126 * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
127 * {@link PinchThresholdManager#THRESHOLD_TWO}, or
128 * {@link PinchThresholdManager#THRESHOLD_THREE}
129 * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
130 * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
Sunny Goyaled268c22016-03-24 15:27:42 -0700131 * Note that this doesn't have to be the opposite of startState;
Tony Wickhamdadb3042016-02-24 11:07:00 -0800132 */
133 public void animateThreshold(float threshold, Workspace.State startState,
134 Workspace.State goingTowards) {
135 if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
136 if (startState == OVERVIEW) {
137 animateOverviewPanelButtons(goingTowards == OVERVIEW);
138 } else if (startState == NORMAL) {
139 animateHotseatAndPageIndicator(goingTowards == NORMAL);
140 animateQsb(goingTowards == NORMAL);
141 }
142 } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
143 if (startState == OVERVIEW) {
144 animateHotseatAndPageIndicator(goingTowards == NORMAL);
145 animateQsb(goingTowards == NORMAL);
146 animateScrim(goingTowards == OVERVIEW);
147 } else if (startState == NORMAL) {
148 animateOverviewPanelButtons(goingTowards == OVERVIEW);
149 animateScrim(goingTowards == OVERVIEW);
150 }
151 } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
152 // Passing threshold 3 ends the pinch and snaps to the new state.
153 if (startState == OVERVIEW && goingTowards == NORMAL) {
154 mLauncher.showWorkspace(true);
155 mWorkspace.snapToPage(mWorkspace.getPageNearestToCenterOfScreen());
156 } else if (startState == NORMAL && goingTowards == OVERVIEW) {
157 mLauncher.showOverviewMode(true);
158 }
159 } else {
160 Log.e(TAG, "Received unknown threshold to animate: " + threshold);
161 }
162 }
163
164 private void setOverviewPanelsAlpha(float alpha, int duration) {
165 mWorkspace.getVisiblePages(mVisiblePageRange);
166 for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
167 View page = mWorkspace.getPageAt(i);
168 if (!mWorkspace.shouldDrawChild(page)) {
169 continue;
170 }
171 if (duration == 0) {
172 ((CellLayout) page).setBackgroundAlpha(alpha);
173 } else {
174 ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
175 .setDuration(duration).start();
176 }
177 }
178 }
179
180 private void animateHotseatAndPageIndicator(boolean show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700181 animateShowHideView(INDEX_HOTSEAT, mLauncher.getHotseat(), show);
182 if (mWorkspace.getPageIndicator() != null) {
183 // There aren't page indicators in landscape mode on phones, hence the null check.
184 animateShowHideView(INDEX_PAGE_INDICATOR, mWorkspace.getPageIndicator(), show);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800185 }
186 }
187
188 private void animateQsb(boolean show) {
189 SearchDropTargetBar.State searchBarState = show ? SearchDropTargetBar.State.SEARCH_BAR
190 : SearchDropTargetBar.State.INVISIBLE;
191 mLauncher.getSearchDropTargetBar().animateToState(searchBarState, THRESHOLD_ANIM_DURATION);
192 }
193
194 private void animateOverviewPanelButtons(boolean show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700195 animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800196 }
197
198 private void animateScrim(boolean show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700199 float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
200 startAnimator(INDEX_SCRIM,
201 ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue),
202 mNormalOverviewTransitionDuration);
203 }
204
205 private void animateShowHideView(int index, final View view, boolean show) {
206 Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer();
Tony Wickhamdadb3042016-02-24 11:07:00 -0800207 if (show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700208 view.setVisibility(View.VISIBLE);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800209 } else {
Sunny Goyaled268c22016-03-24 15:27:42 -0700210 animator.addListener(new AnimatorListenerAdapter() {
211 @Override
212 public void onAnimationEnd(Animator animation) {
213 view.setVisibility(View.INVISIBLE);
214 }
215 });
Tony Wickhamdadb3042016-02-24 11:07:00 -0800216 }
Sunny Goyaled268c22016-03-24 15:27:42 -0700217 startAnimator(index, animator, THRESHOLD_ANIM_DURATION);
218 }
219
220 private void startAnimator(int index, Animator animator, long duration) {
221 if (mAnimators[index] != null) {
222 mAnimators[index].removeAllListeners();
223 mAnimators[index].cancel();
224 }
225 mAnimators[index] = animator;
226 mAnimators[index].setInterpolator(INTERPOLATOR);
227 mAnimators[index].setDuration(duration).start();
Tony Wickhamdadb3042016-02-24 11:07:00 -0800228 }
229}