Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 1 | package com.android.launcher3; |
| 2 | |
| 3 | import android.animation.Animator; |
| 4 | import android.animation.AnimatorListenerAdapter; |
| 5 | import android.animation.ObjectAnimator; |
| 6 | import android.animation.ValueAnimator; |
| 7 | import android.util.Log; |
| 8 | import android.view.View; |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 9 | import android.view.animation.LinearInterpolator; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 10 | |
| 11 | import static com.android.launcher3.Workspace.State.NORMAL; |
| 12 | import 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 | */ |
| 32 | public class PinchAnimationManager { |
| 33 | private static final String TAG = "PinchAnimationManager"; |
| 34 | |
| 35 | private static final int THRESHOLD_ANIM_DURATION = 150; |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 36 | private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator(); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 37 | |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 38 | 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 Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 46 | private Launcher mLauncher; |
| 47 | private Workspace mWorkspace; |
| 48 | |
| 49 | private float mOverviewScale; |
| 50 | private float mOverviewTranslationY; |
| 51 | private int mNormalOverviewTransitionDuration; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 52 | private boolean mIsAnimating; |
| 53 | |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 54 | 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 Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 62 | } |
| 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 Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 131 | * Note that this doesn't have to be the opposite of startState; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 132 | */ |
| 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 Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 181 | 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 Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 185 | } |
| 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 Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 195 | animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 196 | } |
| 197 | |
| 198 | private void animateScrim(boolean show) { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 199 | 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 Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 207 | if (show) { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 208 | view.setVisibility(View.VISIBLE); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 209 | } else { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 210 | animator.addListener(new AnimatorListenerAdapter() { |
| 211 | @Override |
| 212 | public void onAnimationEnd(Animator animation) { |
| 213 | view.setVisibility(View.INVISIBLE); |
| 214 | } |
| 215 | }); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 216 | } |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame^] | 217 | 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 Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 228 | } |
| 229 | } |