Tony Wickham | 1743ac4 | 2016-03-17 11:53:26 -0700 | [diff] [blame^] | 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 | |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 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 | |
| 26 | import static com.android.launcher3.Workspace.State.NORMAL; |
| 27 | import 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 | */ |
| 47 | public 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 | } |