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; |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 25 | import android.view.animation.LinearInterpolator; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 26 | |
Jon Miranda | c4de137 | 2016-10-05 15:40:51 -0700 | [diff] [blame] | 27 | import com.android.launcher3.userevent.nano.LauncherLogProto; |
| 28 | |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 29 | import static com.android.launcher3.Workspace.State.NORMAL; |
| 30 | import 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 | */ |
| 50 | public class PinchAnimationManager { |
| 51 | private static final String TAG = "PinchAnimationManager"; |
| 52 | |
| 53 | private static final int THRESHOLD_ANIM_DURATION = 150; |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 54 | private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator(); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 55 | |
Sunny Goyal | 6178f13 | 2016-07-11 17:30:03 -0700 | [diff] [blame] | 56 | private static final int INDEX_HOTSEAT = 0; |
| 57 | private static final int INDEX_QSB = 1; |
Tony Wickham | f898b97 | 2016-04-05 18:36:36 -0700 | [diff] [blame] | 58 | private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 2; |
| 59 | private static final int INDEX_SCRIM = 3; |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 60 | |
| 61 | private final Animator[] mAnimators = new Animator[4]; |
| 62 | |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 63 | private Launcher mLauncher; |
| 64 | private Workspace mWorkspace; |
| 65 | |
| 66 | private float mOverviewScale; |
| 67 | private float mOverviewTranslationY; |
| 68 | private int mNormalOverviewTransitionDuration; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 69 | private boolean mIsAnimating; |
| 70 | |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 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; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 79 | } |
| 80 | |
| 81 | public int getNormalOverviewTransitionDuration() { |
| 82 | return mNormalOverviewTransitionDuration; |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * Interpolate from {@param currentProgress} to {@param toProgress}, calling |
| 87 | * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1, |
| 88 | * the default overview transition duration is used. |
| 89 | */ |
| 90 | public void animateToProgress(float currentProgress, float toProgress, int duration, |
| 91 | final PinchThresholdManager thresholdManager) { |
| 92 | if (duration == -1) { |
| 93 | duration = mNormalOverviewTransitionDuration; |
| 94 | } |
| 95 | ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress); |
| 96 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
Tony Wickham | f898b97 | 2016-04-05 18:36:36 -0700 | [diff] [blame] | 97 | @Override |
| 98 | public void onAnimationUpdate(ValueAnimator animation) { |
| 99 | float pinchProgress = (Float) animation.getAnimatedValue(); |
| 100 | setAnimationProgress(pinchProgress); |
| 101 | thresholdManager.updateAndAnimatePassedThreshold(pinchProgress, |
| 102 | PinchAnimationManager.this); |
| 103 | } |
| 104 | } |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 105 | ); |
| 106 | animator.addListener(new AnimatorListenerAdapter() { |
| 107 | @Override |
| 108 | public void onAnimationEnd(Animator animation) { |
| 109 | mIsAnimating = false; |
| 110 | thresholdManager.reset(); |
Sunny Goyal | db36437 | 2016-10-26 19:12:47 -0700 | [diff] [blame] | 111 | mWorkspace.onEndStateTransition(); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 112 | } |
| 113 | }); |
| 114 | animator.setDuration(duration).start(); |
| 115 | mIsAnimating = true; |
| 116 | } |
| 117 | |
| 118 | public boolean isAnimating() { |
| 119 | return mIsAnimating; |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Animates to the specified progress. This should be called repeatedly throughout the pinch |
| 124 | * gesture to run animations that interpolate throughout the gesture. |
| 125 | * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace. |
| 126 | */ |
| 127 | public void setAnimationProgress(float interpolatedProgress) { |
| 128 | float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale; |
| 129 | float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY; |
| 130 | mWorkspace.setScaleX(interpolatedScale); |
| 131 | mWorkspace.setScaleY(interpolatedScale); |
| 132 | mWorkspace.setTranslationY(interpolatedTranslationY); |
| 133 | setOverviewPanelsAlpha(1f - interpolatedProgress, 0); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Animates certain properties based on which threshold was passed, and in what direction. The |
| 138 | * starting state must also be taken into account because the thresholds mean different things |
| 139 | * when going from workspace to overview and vice versa. |
| 140 | * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE}, |
| 141 | * {@link PinchThresholdManager#THRESHOLD_TWO}, or |
| 142 | * {@link PinchThresholdManager#THRESHOLD_THREE} |
| 143 | * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}. |
| 144 | * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}. |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 145 | * Note that this doesn't have to be the opposite of startState; |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 146 | */ |
| 147 | public void animateThreshold(float threshold, Workspace.State startState, |
| 148 | Workspace.State goingTowards) { |
| 149 | if (threshold == PinchThresholdManager.THRESHOLD_ONE) { |
| 150 | if (startState == OVERVIEW) { |
| 151 | animateOverviewPanelButtons(goingTowards == OVERVIEW); |
| 152 | } else if (startState == NORMAL) { |
Sunny Goyal | 857bfcf | 2016-07-14 15:30:24 -0700 | [diff] [blame] | 153 | animateHotseatAndQsb(goingTowards == NORMAL); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 154 | } |
| 155 | } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) { |
| 156 | if (startState == OVERVIEW) { |
Sunny Goyal | 857bfcf | 2016-07-14 15:30:24 -0700 | [diff] [blame] | 157 | animateHotseatAndQsb(goingTowards == NORMAL); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 158 | animateScrim(goingTowards == OVERVIEW); |
| 159 | } else if (startState == NORMAL) { |
| 160 | animateOverviewPanelButtons(goingTowards == OVERVIEW); |
| 161 | animateScrim(goingTowards == OVERVIEW); |
| 162 | } |
| 163 | } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) { |
| 164 | // Passing threshold 3 ends the pinch and snaps to the new state. |
| 165 | if (startState == OVERVIEW && goingTowards == NORMAL) { |
Jon Miranda | c4de137 | 2016-10-05 15:40:51 -0700 | [diff] [blame] | 166 | mLauncher.getUserEventDispatcher().logActionOnContainer( |
| 167 | LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE, |
| 168 | LauncherLogProto.OVERVIEW, mWorkspace.getCurrentPage()); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 169 | mLauncher.showWorkspace(true); |
Tony Wickham | f898b97 | 2016-04-05 18:36:36 -0700 | [diff] [blame] | 170 | mWorkspace.snapToPage(mWorkspace.getCurrentPage()); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 171 | } else if (startState == NORMAL && goingTowards == OVERVIEW) { |
Jon Miranda | c4de137 | 2016-10-05 15:40:51 -0700 | [diff] [blame] | 172 | mLauncher.getUserEventDispatcher().logActionOnContainer( |
| 173 | LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE, |
| 174 | LauncherLogProto.WORKSPACE, mWorkspace.getCurrentPage()); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 175 | mLauncher.showOverviewMode(true); |
| 176 | } |
| 177 | } else { |
| 178 | Log.e(TAG, "Received unknown threshold to animate: " + threshold); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | private void setOverviewPanelsAlpha(float alpha, int duration) { |
Sunny Goyal | 9ccafbf | 2016-10-26 13:06:08 -0700 | [diff] [blame] | 183 | int childCount = mWorkspace.getChildCount(); |
| 184 | for (int i = 0; i < childCount; i++) { |
| 185 | final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 186 | if (duration == 0) { |
Sunny Goyal | 9ccafbf | 2016-10-26 13:06:08 -0700 | [diff] [blame] | 187 | cl.setBackgroundAlpha(alpha); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 188 | } else { |
Sunny Goyal | 9ccafbf | 2016-10-26 13:06:08 -0700 | [diff] [blame] | 189 | ObjectAnimator.ofFloat(cl, "backgroundAlpha", alpha).setDuration(duration).start(); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 190 | } |
| 191 | } |
| 192 | } |
| 193 | |
Sunny Goyal | 857bfcf | 2016-07-14 15:30:24 -0700 | [diff] [blame] | 194 | private void animateHotseatAndQsb(boolean show) { |
Sunny Goyal | 6178f13 | 2016-07-11 17:30:03 -0700 | [diff] [blame] | 195 | startAnimator(INDEX_HOTSEAT, |
| 196 | mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION); |
| 197 | startAnimator(INDEX_QSB, mWorkspace.mQsbAlphaController.animateAlphaAtIndex( |
| 198 | show ? 1 : 0, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE), THRESHOLD_ANIM_DURATION); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 199 | } |
| 200 | |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 201 | private void animateOverviewPanelButtons(boolean show) { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 202 | animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 203 | } |
| 204 | |
| 205 | private void animateScrim(boolean show) { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 206 | float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0; |
| 207 | startAnimator(INDEX_SCRIM, |
| 208 | ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue), |
| 209 | mNormalOverviewTransitionDuration); |
| 210 | } |
| 211 | |
| 212 | private void animateShowHideView(int index, final View view, boolean show) { |
| 213 | Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer(); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 214 | if (show) { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 215 | view.setVisibility(View.VISIBLE); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 216 | } else { |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 217 | animator.addListener(new AnimatorListenerAdapter() { |
| 218 | @Override |
| 219 | public void onAnimationEnd(Animator animation) { |
| 220 | view.setVisibility(View.INVISIBLE); |
| 221 | } |
| 222 | }); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 223 | } |
Sunny Goyal | ed268c2 | 2016-03-24 15:27:42 -0700 | [diff] [blame] | 224 | startAnimator(index, animator, THRESHOLD_ANIM_DURATION); |
| 225 | } |
| 226 | |
| 227 | private void startAnimator(int index, Animator animator, long duration) { |
| 228 | if (mAnimators[index] != null) { |
| 229 | mAnimators[index].removeAllListeners(); |
| 230 | mAnimators[index].cancel(); |
| 231 | } |
| 232 | mAnimators[index] = animator; |
| 233 | mAnimators[index].setInterpolator(INTERPOLATOR); |
| 234 | mAnimators[index].setDuration(duration).start(); |
Tony Wickham | dadb304 | 2016-02-24 11:07:00 -0800 | [diff] [blame] | 235 | } |
| 236 | } |