1 /* 2 * Copyright (C) 2015 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 package com.android.launcher3.allapps; 17 18 import static com.android.launcher3.LauncherState.ALL_APPS; 19 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; 20 import static com.android.launcher3.LauncherState.NORMAL; 21 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; 22 import static com.android.launcher3.anim.Interpolators.LINEAR; 23 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; 24 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 25 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; 26 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS; 27 28 import android.animation.Animator; 29 import android.animation.Animator.AnimatorListener; 30 import android.animation.ObjectAnimator; 31 import android.util.FloatProperty; 32 import android.view.HapticFeedbackConstants; 33 import android.view.View; 34 import android.view.animation.Interpolator; 35 36 import com.android.launcher3.DeviceProfile; 37 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 38 import com.android.launcher3.Launcher; 39 import com.android.launcher3.LauncherState; 40 import com.android.launcher3.Utilities; 41 import com.android.launcher3.anim.AnimatorListeners; 42 import com.android.launcher3.anim.PendingAnimation; 43 import com.android.launcher3.anim.PropertySetter; 44 import com.android.launcher3.config.FeatureFlags; 45 import com.android.launcher3.statemanager.StateManager.StateHandler; 46 import com.android.launcher3.states.StateAnimationConfig; 47 import com.android.launcher3.views.ScrimView; 48 49 /** 50 * Handles AllApps view transition. 51 * 1) Slides all apps view using direct manipulation 52 * 2) When finger is released, animate to either top or bottom accordingly. 53 * <p/> 54 * Algorithm: 55 * If release velocity > THRES1, snap according to the direction of movement. 56 * If release velocity < THRES1, snap according to either top or bottom depending on whether it's 57 * closer to top or closer to the page indicator. 58 */ 59 public class AllAppsTransitionController 60 implements StateHandler<LauncherState>, OnDeviceProfileChangeListener { 61 // This constant should match the second derivative of the animator interpolator. 62 public static final float INTERP_COEFF = 1.7f; 63 64 public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS = 65 new FloatProperty<AllAppsTransitionController>("allAppsProgress") { 66 67 @Override 68 public Float get(AllAppsTransitionController controller) { 69 return controller.mProgress; 70 } 71 72 @Override 73 public void setValue(AllAppsTransitionController controller, float progress) { 74 controller.setProgress(progress); 75 } 76 }; 77 78 private AllAppsContainerView mAppsView; 79 80 private final Launcher mLauncher; 81 private boolean mIsVerticalLayout; 82 83 // Animation in this class is controlled by a single variable {@link mProgress}. 84 // Visually, it represents top y coordinate of the all apps container if multiplied with 85 // {@link mShiftRange}. 86 87 // When {@link mProgress} is 0, all apps container is pulled up. 88 // When {@link mProgress} is 1, all apps container is pulled down. 89 private float mShiftRange; // changes depending on the orientation 90 private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent 91 92 private float mScrollRangeDelta = 0; 93 private ScrimView mScrimView; 94 AllAppsTransitionController(Launcher l)95 public AllAppsTransitionController(Launcher l) { 96 mLauncher = l; 97 mShiftRange = mLauncher.getDeviceProfile().heightPx; 98 mProgress = 1f; 99 100 mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout(); 101 mLauncher.addOnDeviceProfileChangeListener(this); 102 } 103 getShiftRange()104 public float getShiftRange() { 105 return mShiftRange; 106 } 107 108 @Override onDeviceProfileChanged(DeviceProfile dp)109 public void onDeviceProfileChanged(DeviceProfile dp) { 110 mIsVerticalLayout = dp.isVerticalBarLayout(); 111 setScrollRangeDelta(mScrollRangeDelta); 112 113 if (mIsVerticalLayout) { 114 mLauncher.getHotseat().setTranslationY(0); 115 mLauncher.getWorkspace().getPageIndicator().setTranslationY(0); 116 } 117 } 118 119 /** 120 * Note this method should not be called outside this class. This is public because it is used 121 * in xml-based animations which also handle updating the appropriate UI. 122 * 123 * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace 124 * @see #setState(LauncherState) 125 * @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation) 126 */ setProgress(float progress)127 public void setProgress(float progress) { 128 mProgress = progress; 129 mAppsView.setTranslationY(mProgress * mShiftRange); 130 } 131 getProgress()132 public float getProgress() { 133 return mProgress; 134 } 135 136 /** 137 * Sets the vertical transition progress to {@param state} and updates all the dependent UI 138 * accordingly. 139 */ 140 @Override setState(LauncherState state)141 public void setState(LauncherState state) { 142 setProgress(state.getVerticalProgress(mLauncher)); 143 setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER); 144 onProgressAnimationEnd(); 145 } 146 147 /** 148 * Creates an animation which updates the vertical transition progress and updates all the 149 * dependent UI using various animation events 150 */ 151 @Override setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation builder)152 public void setStateWithAnimation(LauncherState toState, 153 StateAnimationConfig config, PendingAnimation builder) { 154 float targetProgress = toState.getVerticalProgress(mLauncher); 155 if (Float.compare(mProgress, targetProgress) == 0) { 156 setAlphas(toState, config, builder); 157 // Fail fast 158 onProgressAnimationEnd(); 159 return; 160 } 161 162 // need to decide depending on the release velocity 163 Interpolator interpolator = (config.userControlled ? LINEAR : DEACCEL_1_7); 164 165 Animator anim = createSpringAnimation(mProgress, targetProgress); 166 anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator)); 167 anim.addListener(getProgressAnimatorListener()); 168 builder.add(anim); 169 170 setAlphas(toState, config, builder); 171 172 if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) { 173 mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 174 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 175 } 176 } 177 createSpringAnimation(float... progressValues)178 public Animator createSpringAnimation(float... progressValues) { 179 return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues); 180 } 181 182 /** 183 * Updates the property for the provided state 184 */ setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter)185 public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) { 186 int visibleElements = state.getVisibleElements(mLauncher); 187 boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0; 188 189 Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR); 190 setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade); 191 192 boolean shouldProtectHeader = 193 ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS; 194 mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null); 195 } 196 getProgressAnimatorListener()197 public AnimatorListener getProgressAnimatorListener() { 198 return AnimatorListeners.forSuccessCallback(this::onProgressAnimationEnd); 199 } 200 201 /** 202 * see Launcher#setupViews 203 */ setupViews(ScrimView scrimView, AllAppsContainerView appsView)204 public void setupViews(ScrimView scrimView, AllAppsContainerView appsView) { 205 mScrimView = scrimView; 206 mAppsView = appsView; 207 if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && Utilities.ATLEAST_R) { 208 mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS, 209 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 210 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 211 } 212 mAppsView.setScrimView(scrimView); 213 } 214 215 /** 216 * Updates the total scroll range but does not update the UI. 217 */ setScrollRangeDelta(float delta)218 public void setScrollRangeDelta(float delta) { 219 mScrollRangeDelta = delta; 220 mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta; 221 } 222 223 /** 224 * Set the final view states based on the progress. 225 * TODO: This logic should go in {@link LauncherState} 226 */ onProgressAnimationEnd()227 private void onProgressAnimationEnd() { 228 if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return; 229 if (Float.compare(mProgress, 1f) == 0) { 230 mAppsView.reset(false /* animate */); 231 } 232 } 233 } 234