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.anim.Interpolators.DEACCEL_1_7; 21 import static com.android.launcher3.anim.Interpolators.LINEAR; 22 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; 23 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 24 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; 25 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS; 26 27 import android.animation.Animator; 28 import android.animation.Animator.AnimatorListener; 29 import android.animation.ObjectAnimator; 30 import android.util.FloatProperty; 31 import android.view.View; 32 import android.view.animation.Interpolator; 33 34 import com.android.launcher3.DeviceProfile; 35 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.LauncherState; 38 import com.android.launcher3.Utilities; 39 import com.android.launcher3.anim.AnimatorListeners; 40 import com.android.launcher3.anim.Interpolators; 41 import com.android.launcher3.anim.PendingAnimation; 42 import com.android.launcher3.anim.PropertySetter; 43 import com.android.launcher3.config.FeatureFlags; 44 import com.android.launcher3.statemanager.StateManager.StateHandler; 45 import com.android.launcher3.states.StateAnimationConfig; 46 import com.android.launcher3.views.ScrimView; 47 48 /** 49 * Handles AllApps view transition. 50 * 1) Slides all apps view using direct manipulation 51 * 2) When finger is released, animate to either top or bottom accordingly. 52 * <p/> 53 * Algorithm: 54 * If release velocity > THRES1, snap according to the direction of movement. 55 * If release velocity < THRES1, snap according to either top or bottom depending on whether it's 56 * closer to top or closer to the page indicator. 57 */ 58 public class AllAppsTransitionController 59 implements StateHandler<LauncherState>, OnDeviceProfileChangeListener { 60 // This constant should match the second derivative of the animator interpolator. 61 public static final float INTERP_COEFF = 1.7f; 62 private static final float CONTENT_VISIBLE_MAX_THRESHOLD = 0.5f; 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 createSpringAnimation(float... progressValues)173 public Animator createSpringAnimation(float... progressValues) { 174 return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues); 175 } 176 177 /** 178 * Updates the property for the provided state 179 */ setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter)180 public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) { 181 int visibleElements = state.getVisibleElements(mLauncher); 182 boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0; 183 184 Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, 185 Interpolators.clampToProgress(LINEAR, 0, CONTENT_VISIBLE_MAX_THRESHOLD)); 186 setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade); 187 188 boolean shouldProtectHeader = 189 ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS; 190 mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null); 191 } 192 getProgressAnimatorListener()193 public AnimatorListener getProgressAnimatorListener() { 194 return AnimatorListeners.forSuccessCallback(this::onProgressAnimationEnd); 195 } 196 197 /** 198 * see Launcher#setupViews 199 */ setupViews(ScrimView scrimView, AllAppsContainerView appsView)200 public void setupViews(ScrimView scrimView, AllAppsContainerView appsView) { 201 mScrimView = scrimView; 202 mAppsView = appsView; 203 if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && Utilities.ATLEAST_R) { 204 mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS, 205 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 206 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 207 } 208 mAppsView.setScrimView(scrimView); 209 } 210 211 /** 212 * Updates the total scroll range but does not update the UI. 213 */ setScrollRangeDelta(float delta)214 public void setScrollRangeDelta(float delta) { 215 mScrollRangeDelta = delta; 216 mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta; 217 } 218 219 /** 220 * Set the final view states based on the progress. 221 * TODO: This logic should go in {@link LauncherState} 222 */ onProgressAnimationEnd()223 private void onProgressAnimationEnd() { 224 if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return; 225 if (Float.compare(mProgress, 1f) == 0) { 226 mAppsView.reset(false /* animate */); 227 } 228 } 229 } 230