blob: 84ef12e16708256187b1e09a6e80dea7b835b47e [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;
Sunny Goyaled268c22016-03-24 15:27:42 -070025import android.view.animation.LinearInterpolator;
Tony Wickhamdadb3042016-02-24 11:07:00 -080026
Jon Mirandac4de1372016-10-05 15:40:51 -070027import com.android.launcher3.userevent.nano.LauncherLogProto;
28
Tony Wickhamdadb3042016-02-24 11:07:00 -080029import static com.android.launcher3.Workspace.State.NORMAL;
30import static com.android.launcher3.Workspace.State.OVERVIEW;
31
32/**
33 * Manages the animations that play as the user pinches to/from overview mode.
34 *
35 * It will look like this pinching in:
36 * - Workspace scales down
37 * - At some threshold 1, hotseat and QSB fade out (full animation)
38 * - At a later threshold 2, panel buttons fade in and scrim fades in
39 * - At a final threshold 3, snap to overview
40 *
41 * Pinching out:
42 * - Workspace scales up
43 * - At threshold 1, panel buttons fade out
44 * - At threshold 2, hotseat and QSB fade in and scrim fades out
45 * - At threshold 3, snap to workspace
46 *
47 * @see PinchToOverviewListener
48 * @see PinchThresholdManager
49 */
50public class PinchAnimationManager {
51 private static final String TAG = "PinchAnimationManager";
52
53 private static final int THRESHOLD_ANIM_DURATION = 150;
Sunny Goyaled268c22016-03-24 15:27:42 -070054 private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
Tony Wickhamdadb3042016-02-24 11:07:00 -080055
Sunny Goyal6178f132016-07-11 17:30:03 -070056 private static final int INDEX_HOTSEAT = 0;
57 private static final int INDEX_QSB = 1;
Tony Wickhamf898b972016-04-05 18:36:36 -070058 private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 2;
59 private static final int INDEX_SCRIM = 3;
Sunny Goyaled268c22016-03-24 15:27:42 -070060
61 private final Animator[] mAnimators = new Animator[4];
62
63 private final int[] mVisiblePageRange = new int[2];
Tony Wickhamdadb3042016-02-24 11:07:00 -080064 private Launcher mLauncher;
65 private Workspace mWorkspace;
66
67 private float mOverviewScale;
68 private float mOverviewTranslationY;
69 private int mNormalOverviewTransitionDuration;
Tony Wickhamdadb3042016-02-24 11:07:00 -080070 private boolean mIsAnimating;
71
Tony Wickhamdadb3042016-02-24 11:07:00 -080072 public PinchAnimationManager(Launcher launcher) {
73 mLauncher = launcher;
74 mWorkspace = launcher.mWorkspace;
75
76 mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
77 mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
78 mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
79 .mOverviewTransitionTime;
Tony Wickhamdadb3042016-02-24 11:07:00 -080080 }
81
82 public int getNormalOverviewTransitionDuration() {
83 return mNormalOverviewTransitionDuration;
84 }
85
86 /**
87 * Interpolate from {@param currentProgress} to {@param toProgress}, calling
88 * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
89 * the default overview transition duration is used.
90 */
91 public void animateToProgress(float currentProgress, float toProgress, int duration,
92 final PinchThresholdManager thresholdManager) {
93 if (duration == -1) {
94 duration = mNormalOverviewTransitionDuration;
95 }
96 ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
97 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
Tony Wickhamf898b972016-04-05 18:36:36 -070098 @Override
99 public void onAnimationUpdate(ValueAnimator animation) {
100 float pinchProgress = (Float) animation.getAnimatedValue();
101 setAnimationProgress(pinchProgress);
102 thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
103 PinchAnimationManager.this);
104 }
105 }
Tony Wickhamdadb3042016-02-24 11:07:00 -0800106 );
107 animator.addListener(new AnimatorListenerAdapter() {
108 @Override
109 public void onAnimationEnd(Animator animation) {
110 mIsAnimating = false;
111 thresholdManager.reset();
Tony Wickhamf898b972016-04-05 18:36:36 -0700112 mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800113 }
114 });
115 animator.setDuration(duration).start();
116 mIsAnimating = true;
117 }
118
119 public boolean isAnimating() {
120 return mIsAnimating;
121 }
122
123 /**
124 * Animates to the specified progress. This should be called repeatedly throughout the pinch
125 * gesture to run animations that interpolate throughout the gesture.
126 * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
127 */
128 public void setAnimationProgress(float interpolatedProgress) {
129 float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
130 float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
131 mWorkspace.setScaleX(interpolatedScale);
132 mWorkspace.setScaleY(interpolatedScale);
133 mWorkspace.setTranslationY(interpolatedTranslationY);
134 setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800135 }
136
137 /**
138 * Animates certain properties based on which threshold was passed, and in what direction. The
139 * starting state must also be taken into account because the thresholds mean different things
140 * when going from workspace to overview and vice versa.
141 * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
142 * {@link PinchThresholdManager#THRESHOLD_TWO}, or
143 * {@link PinchThresholdManager#THRESHOLD_THREE}
144 * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
145 * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
Sunny Goyaled268c22016-03-24 15:27:42 -0700146 * Note that this doesn't have to be the opposite of startState;
Tony Wickhamdadb3042016-02-24 11:07:00 -0800147 */
148 public void animateThreshold(float threshold, Workspace.State startState,
149 Workspace.State goingTowards) {
150 if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
151 if (startState == OVERVIEW) {
152 animateOverviewPanelButtons(goingTowards == OVERVIEW);
153 } else if (startState == NORMAL) {
Sunny Goyal857bfcf2016-07-14 15:30:24 -0700154 animateHotseatAndQsb(goingTowards == NORMAL);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800155 }
156 } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
157 if (startState == OVERVIEW) {
Sunny Goyal857bfcf2016-07-14 15:30:24 -0700158 animateHotseatAndQsb(goingTowards == NORMAL);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800159 animateScrim(goingTowards == OVERVIEW);
160 } else if (startState == NORMAL) {
161 animateOverviewPanelButtons(goingTowards == OVERVIEW);
162 animateScrim(goingTowards == OVERVIEW);
163 }
164 } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
165 // Passing threshold 3 ends the pinch and snaps to the new state.
166 if (startState == OVERVIEW && goingTowards == NORMAL) {
Jon Mirandac4de1372016-10-05 15:40:51 -0700167 mLauncher.getUserEventDispatcher().logActionOnContainer(
168 LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE,
169 LauncherLogProto.OVERVIEW, mWorkspace.getCurrentPage());
Tony Wickhamdadb3042016-02-24 11:07:00 -0800170 mLauncher.showWorkspace(true);
Tony Wickhamf898b972016-04-05 18:36:36 -0700171 mWorkspace.snapToPage(mWorkspace.getCurrentPage());
Tony Wickhamdadb3042016-02-24 11:07:00 -0800172 } else if (startState == NORMAL && goingTowards == OVERVIEW) {
Jon Mirandac4de1372016-10-05 15:40:51 -0700173 mLauncher.getUserEventDispatcher().logActionOnContainer(
174 LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE,
175 LauncherLogProto.WORKSPACE, mWorkspace.getCurrentPage());
Tony Wickhamdadb3042016-02-24 11:07:00 -0800176 mLauncher.showOverviewMode(true);
177 }
178 } else {
179 Log.e(TAG, "Received unknown threshold to animate: " + threshold);
180 }
181 }
182
183 private void setOverviewPanelsAlpha(float alpha, int duration) {
184 mWorkspace.getVisiblePages(mVisiblePageRange);
185 for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
186 View page = mWorkspace.getPageAt(i);
187 if (!mWorkspace.shouldDrawChild(page)) {
188 continue;
189 }
190 if (duration == 0) {
191 ((CellLayout) page).setBackgroundAlpha(alpha);
192 } else {
193 ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
194 .setDuration(duration).start();
195 }
196 }
197 }
198
Sunny Goyal857bfcf2016-07-14 15:30:24 -0700199 private void animateHotseatAndQsb(boolean show) {
Sunny Goyal6178f132016-07-11 17:30:03 -0700200 startAnimator(INDEX_HOTSEAT,
201 mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION);
202 startAnimator(INDEX_QSB, mWorkspace.mQsbAlphaController.animateAlphaAtIndex(
203 show ? 1 : 0, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE), THRESHOLD_ANIM_DURATION);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800204 }
205
Tony Wickhamdadb3042016-02-24 11:07:00 -0800206 private void animateOverviewPanelButtons(boolean show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700207 animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800208 }
209
210 private void animateScrim(boolean show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700211 float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
212 startAnimator(INDEX_SCRIM,
213 ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue),
214 mNormalOverviewTransitionDuration);
215 }
216
217 private void animateShowHideView(int index, final View view, boolean show) {
218 Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer();
Tony Wickhamdadb3042016-02-24 11:07:00 -0800219 if (show) {
Sunny Goyaled268c22016-03-24 15:27:42 -0700220 view.setVisibility(View.VISIBLE);
Tony Wickhamdadb3042016-02-24 11:07:00 -0800221 } else {
Sunny Goyaled268c22016-03-24 15:27:42 -0700222 animator.addListener(new AnimatorListenerAdapter() {
223 @Override
224 public void onAnimationEnd(Animator animation) {
225 view.setVisibility(View.INVISIBLE);
226 }
227 });
Tony Wickhamdadb3042016-02-24 11:07:00 -0800228 }
Sunny Goyaled268c22016-03-24 15:27:42 -0700229 startAnimator(index, animator, THRESHOLD_ANIM_DURATION);
230 }
231
232 private void startAnimator(int index, Animator animator, long duration) {
233 if (mAnimators[index] != null) {
234 mAnimators[index].removeAllListeners();
235 mAnimators[index].cancel();
236 }
237 mAnimators[index] = animator;
238 mAnimators[index].setInterpolator(INTERPOLATOR);
239 mAnimators[index].setDuration(duration).start();
Tony Wickhamdadb3042016-02-24 11:07:00 -0800240 }
241}