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 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 import android.view.animation.LinearInterpolator; 26 27 import static com.android.launcher3.Workspace.State.NORMAL; 28 import static com.android.launcher3.Workspace.State.OVERVIEW; 29 30 /** 31 * Manages the animations that play as the user pinches to/from overview mode. 32 * 33 * It will look like this pinching in: 34 * - Workspace scales down 35 * - At some threshold 1, hotseat and QSB fade out (full animation) 36 * - At a later threshold 2, panel buttons fade in and scrim fades in 37 * - At a final threshold 3, snap to overview 38 * 39 * Pinching out: 40 * - Workspace scales up 41 * - At threshold 1, panel buttons fade out 42 * - At threshold 2, hotseat and QSB fade in and scrim fades out 43 * - At threshold 3, snap to workspace 44 * 45 * @see PinchToOverviewListener 46 * @see PinchThresholdManager 47 */ 48 public class PinchAnimationManager { 49 private static final String TAG = "PinchAnimationManager"; 50 51 private static final int THRESHOLD_ANIM_DURATION = 150; 52 private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator(); 53 54 private static final int INDEX_HOTSEAT = 0; 55 private static final int INDEX_QSB = 1; 56 private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 2; 57 private static final int INDEX_SCRIM = 3; 58 59 private final Animator[] mAnimators = new Animator[4]; 60 61 private final int[] mVisiblePageRange = new int[2]; 62 private Launcher mLauncher; 63 private Workspace mWorkspace; 64 65 private float mOverviewScale; 66 private float mOverviewTranslationY; 67 private int mNormalOverviewTransitionDuration; 68 private boolean mIsAnimating; 69 PinchAnimationManager(Launcher launcher)70 public PinchAnimationManager(Launcher launcher) { 71 mLauncher = launcher; 72 mWorkspace = launcher.mWorkspace; 73 74 mOverviewScale = mWorkspace.getOverviewModeShrinkFactor(); 75 mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY(); 76 mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation() 77 .mOverviewTransitionTime; 78 } 79 getNormalOverviewTransitionDuration()80 public int getNormalOverviewTransitionDuration() { 81 return mNormalOverviewTransitionDuration; 82 } 83 84 /** 85 * Interpolate from {@param currentProgress} to {@param toProgress}, calling 86 * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1, 87 * the default overview transition duration is used. 88 */ animateToProgress(float currentProgress, float toProgress, int duration, final PinchThresholdManager thresholdManager)89 public void animateToProgress(float currentProgress, float toProgress, int duration, 90 final PinchThresholdManager thresholdManager) { 91 if (duration == -1) { 92 duration = mNormalOverviewTransitionDuration; 93 } 94 ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress); 95 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 96 @Override 97 public void onAnimationUpdate(ValueAnimator animation) { 98 float pinchProgress = (Float) animation.getAnimatedValue(); 99 setAnimationProgress(pinchProgress); 100 thresholdManager.updateAndAnimatePassedThreshold(pinchProgress, 101 PinchAnimationManager.this); 102 } 103 } 104 ); 105 animator.addListener(new AnimatorListenerAdapter() { 106 @Override 107 public void onAnimationEnd(Animator animation) { 108 mIsAnimating = false; 109 thresholdManager.reset(); 110 mWorkspace.onLauncherTransitionEnd(mLauncher, false, true); 111 } 112 }); 113 animator.setDuration(duration).start(); 114 mIsAnimating = true; 115 } 116 isAnimating()117 public boolean isAnimating() { 118 return mIsAnimating; 119 } 120 121 /** 122 * Animates to the specified progress. This should be called repeatedly throughout the pinch 123 * gesture to run animations that interpolate throughout the gesture. 124 * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace. 125 */ setAnimationProgress(float interpolatedProgress)126 public void setAnimationProgress(float interpolatedProgress) { 127 float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale; 128 float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY; 129 mWorkspace.setScaleX(interpolatedScale); 130 mWorkspace.setScaleY(interpolatedScale); 131 mWorkspace.setTranslationY(interpolatedTranslationY); 132 setOverviewPanelsAlpha(1f - interpolatedProgress, 0); 133 } 134 135 /** 136 * Animates certain properties based on which threshold was passed, and in what direction. The 137 * starting state must also be taken into account because the thresholds mean different things 138 * when going from workspace to overview and vice versa. 139 * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE}, 140 * {@link PinchThresholdManager#THRESHOLD_TWO}, or 141 * {@link PinchThresholdManager#THRESHOLD_THREE} 142 * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}. 143 * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}. 144 * Note that this doesn't have to be the opposite of startState; 145 */ animateThreshold(float threshold, Workspace.State startState, Workspace.State goingTowards)146 public void animateThreshold(float threshold, Workspace.State startState, 147 Workspace.State goingTowards) { 148 if (threshold == PinchThresholdManager.THRESHOLD_ONE) { 149 if (startState == OVERVIEW) { 150 animateOverviewPanelButtons(goingTowards == OVERVIEW); 151 } else if (startState == NORMAL) { 152 animateHotseatAndQsb(goingTowards == NORMAL); 153 } 154 } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) { 155 if (startState == OVERVIEW) { 156 animateHotseatAndQsb(goingTowards == NORMAL); 157 animateScrim(goingTowards == OVERVIEW); 158 } else if (startState == NORMAL) { 159 animateOverviewPanelButtons(goingTowards == OVERVIEW); 160 animateScrim(goingTowards == OVERVIEW); 161 } 162 } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) { 163 // Passing threshold 3 ends the pinch and snaps to the new state. 164 if (startState == OVERVIEW && goingTowards == NORMAL) { 165 mLauncher.showWorkspace(true); 166 mWorkspace.snapToPage(mWorkspace.getCurrentPage()); 167 } else if (startState == NORMAL && goingTowards == OVERVIEW) { 168 mLauncher.showOverviewMode(true); 169 } 170 } else { 171 Log.e(TAG, "Received unknown threshold to animate: " + threshold); 172 } 173 } 174 setOverviewPanelsAlpha(float alpha, int duration)175 private void setOverviewPanelsAlpha(float alpha, int duration) { 176 mWorkspace.getVisiblePages(mVisiblePageRange); 177 for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) { 178 View page = mWorkspace.getPageAt(i); 179 if (!mWorkspace.shouldDrawChild(page)) { 180 continue; 181 } 182 if (duration == 0) { 183 ((CellLayout) page).setBackgroundAlpha(alpha); 184 } else { 185 ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha) 186 .setDuration(duration).start(); 187 } 188 } 189 } 190 animateHotseatAndQsb(boolean show)191 private void animateHotseatAndQsb(boolean show) { 192 startAnimator(INDEX_HOTSEAT, 193 mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION); 194 startAnimator(INDEX_QSB, mWorkspace.mQsbAlphaController.animateAlphaAtIndex( 195 show ? 1 : 0, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE), THRESHOLD_ANIM_DURATION); 196 } 197 animateOverviewPanelButtons(boolean show)198 private void animateOverviewPanelButtons(boolean show) { 199 animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show); 200 } 201 animateScrim(boolean show)202 private void animateScrim(boolean show) { 203 float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0; 204 startAnimator(INDEX_SCRIM, 205 ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue), 206 mNormalOverviewTransitionDuration); 207 } 208 animateShowHideView(int index, final View view, boolean show)209 private void animateShowHideView(int index, final View view, boolean show) { 210 Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer(); 211 if (show) { 212 view.setVisibility(View.VISIBLE); 213 } else { 214 animator.addListener(new AnimatorListenerAdapter() { 215 @Override 216 public void onAnimationEnd(Animator animation) { 217 view.setVisibility(View.INVISIBLE); 218 } 219 }); 220 } 221 startAnimator(index, animator, THRESHOLD_ANIM_DURATION); 222 } 223 startAnimator(int index, Animator animator, long duration)224 private void startAnimator(int index, Animator animator, long duration) { 225 if (mAnimators[index] != null) { 226 mAnimators[index].removeAllListeners(); 227 mAnimators[index].cancel(); 228 } 229 mAnimators[index] = animator; 230 mAnimators[index].setInterpolator(INTERPOLATOR); 231 mAnimators[index].setDuration(duration).start(); 232 } 233 } 234