1 package com.android.launcher3.allapps; 2 3 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; 4 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; 5 import static com.android.launcher3.LauncherState.OVERVIEW; 6 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; 7 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; 8 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE; 9 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; 10 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; 11 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 12 import static com.android.launcher3.anim.Interpolators.LINEAR; 13 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; 14 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; 15 16 import android.animation.Animator; 17 import android.animation.AnimatorListenerAdapter; 18 import android.util.FloatProperty; 19 import android.view.animation.Interpolator; 20 21 import com.android.launcher3.DeviceProfile; 22 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 23 import com.android.launcher3.Launcher; 24 import com.android.launcher3.LauncherState; 25 import com.android.launcher3.LauncherStateManager.AnimationConfig; 26 import com.android.launcher3.LauncherStateManager.StateHandler; 27 import com.android.launcher3.R; 28 import com.android.launcher3.anim.AnimationSuccessListener; 29 import com.android.launcher3.anim.AnimatorSetBuilder; 30 import com.android.launcher3.anim.PropertySetter; 31 import com.android.launcher3.anim.SpringObjectAnimator; 32 import com.android.launcher3.util.Themes; 33 import com.android.launcher3.views.ScrimView; 34 35 /** 36 * Handles AllApps view transition. 37 * 1) Slides all apps view using direct manipulation 38 * 2) When finger is released, animate to either top or bottom accordingly. 39 * <p/> 40 * Algorithm: 41 * If release velocity > THRES1, snap according to the direction of movement. 42 * If release velocity < THRES1, snap according to either top or bottom depending on whether it's 43 * closer to top or closer to the page indicator. 44 */ 45 public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener { 46 47 private static final float SPRING_DAMPING_RATIO = 0.9f; 48 private static final float SPRING_STIFFNESS = 600f; 49 50 public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS = 51 new FloatProperty<AllAppsTransitionController>("allAppsProgress") { 52 53 @Override 54 public Float get(AllAppsTransitionController controller) { 55 return controller.mProgress; 56 } 57 58 @Override 59 public void setValue(AllAppsTransitionController controller, float progress) { 60 controller.setProgress(progress); 61 } 62 }; 63 64 private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0; 65 66 private AllAppsContainerView mAppsView; 67 private ScrimView mScrimView; 68 69 private final Launcher mLauncher; 70 private final boolean mIsDarkTheme; 71 private boolean mIsVerticalLayout; 72 73 // Animation in this class is controlled by a single variable {@link mProgress}. 74 // Visually, it represents top y coordinate of the all apps container if multiplied with 75 // {@link mShiftRange}. 76 77 // When {@link mProgress} is 0, all apps container is pulled up. 78 // When {@link mProgress} is 1, all apps container is pulled down. 79 private float mShiftRange; // changes depending on the orientation 80 private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent 81 82 private float mScrollRangeDelta = 0; 83 AllAppsTransitionController(Launcher l)84 public AllAppsTransitionController(Launcher l) { 85 mLauncher = l; 86 mShiftRange = mLauncher.getDeviceProfile().heightPx; 87 mProgress = 1f; 88 89 mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); 90 mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout(); 91 mLauncher.addOnDeviceProfileChangeListener(this); 92 } 93 getShiftRange()94 public float getShiftRange() { 95 return mShiftRange; 96 } 97 98 @Override onDeviceProfileChanged(DeviceProfile dp)99 public void onDeviceProfileChanged(DeviceProfile dp) { 100 mIsVerticalLayout = dp.isVerticalBarLayout(); 101 setScrollRangeDelta(mScrollRangeDelta); 102 103 if (mIsVerticalLayout) { 104 mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1); 105 mLauncher.getHotseat().setTranslationY(0); 106 mLauncher.getWorkspace().getPageIndicator().setTranslationY(0); 107 } 108 } 109 110 /** 111 * Note this method should not be called outside this class. This is public because it is used 112 * in xml-based animations which also handle updating the appropriate UI. 113 * 114 * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace 115 * 116 * @see #setState(LauncherState) 117 * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig) 118 */ setProgress(float progress)119 public void setProgress(float progress) { 120 mProgress = progress; 121 mScrimView.setProgress(progress); 122 float shiftCurrent = progress * mShiftRange; 123 124 mAppsView.setTranslationY(shiftCurrent); 125 126 // Use a light system UI (dark icons) if all apps is behind at least half of the 127 // status bar. 128 boolean forceChange = shiftCurrent - mScrimView.getDragHandleSize() 129 <= mLauncher.getDeviceProfile().getInsets().top / 2; 130 if (forceChange) { 131 mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme); 132 } else { 133 mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0); 134 } 135 } 136 getProgress()137 public float getProgress() { 138 return mProgress; 139 } 140 141 /** 142 * Sets the vertical transition progress to {@param state} and updates all the dependent UI 143 * accordingly. 144 */ 145 @Override setState(LauncherState state)146 public void setState(LauncherState state) { 147 setProgress(state.getVerticalProgress(mLauncher)); 148 setAlphas(state, null, new AnimatorSetBuilder()); 149 onProgressAnimationEnd(); 150 } 151 152 /** 153 * Creates an animation which updates the vertical transition progress and updates all the 154 * dependent UI using various animation events 155 */ 156 @Override setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder, AnimationConfig config)157 public void setStateWithAnimation(LauncherState toState, 158 AnimatorSetBuilder builder, AnimationConfig config) { 159 float targetProgress = toState.getVerticalProgress(mLauncher); 160 if (Float.compare(mProgress, targetProgress) == 0) { 161 setAlphas(toState, config, builder); 162 // Fail fast 163 onProgressAnimationEnd(); 164 return; 165 } 166 167 if (!config.playNonAtomicComponent()) { 168 // There is no atomic component for the all apps transition, so just return early. 169 return; 170 } 171 172 Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW 173 ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN) 174 : FAST_OUT_SLOW_IN; 175 Animator anim = createSpringAnimation(mProgress, targetProgress); 176 anim.setDuration(config.duration); 177 anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator)); 178 anim.addListener(getProgressAnimatorListener()); 179 180 builder.play(anim); 181 182 setAlphas(toState, config, builder); 183 } 184 createSpringAnimation(float... progressValues)185 public Animator createSpringAnimation(float... progressValues) { 186 return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange, 187 SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues); 188 } 189 setAlphas(LauncherState toState, AnimationConfig config, AnimatorSetBuilder builder)190 private void setAlphas(LauncherState toState, AnimationConfig config, 191 AnimatorSetBuilder builder) { 192 setAlphas(toState.getVisibleElements(mLauncher), config, builder); 193 } 194 setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder)195 public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) { 196 PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER 197 : config.getPropertySetter(builder); 198 boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0; 199 boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0; 200 201 Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR); 202 Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade); 203 setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade); 204 setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade); 205 mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasAllAppsContent, 206 setter, headerFade, allAppsFade); 207 mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade); 208 209 setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA, 210 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade); 211 } 212 getProgressAnimatorListener()213 public AnimatorListenerAdapter getProgressAnimatorListener() { 214 return new AnimationSuccessListener() { 215 @Override 216 public void onAnimationSuccess(Animator animator) { 217 onProgressAnimationEnd(); 218 } 219 }; 220 } 221 222 public void setupViews(AllAppsContainerView appsView) { 223 mAppsView = appsView; 224 mScrimView = mLauncher.findViewById(R.id.scrim_view); 225 } 226 227 /** 228 * Updates the total scroll range but does not update the UI. 229 */ 230 void setScrollRangeDelta(float delta) { 231 mScrollRangeDelta = delta; 232 mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta; 233 234 if (mScrimView != null) { 235 mScrimView.reInitUi(); 236 } 237 } 238 239 /** 240 * Set the final view states based on the progress. 241 * TODO: This logic should go in {@link LauncherState} 242 */ 243 private void onProgressAnimationEnd() { 244 if (Float.compare(mProgress, 1f) == 0) { 245 mAppsView.reset(false /* animate */); 246 } else if (isAllAppsExpanded()) { 247 mAppsView.onScrollUpEnd(); 248 } 249 } 250 251 private boolean isAllAppsExpanded() { 252 return Float.compare(mProgress, 0f) == 0; 253 } 254 255 public void highlightWorkTabIfNecessary() { 256 if (isAllAppsExpanded()) { 257 mAppsView.highlightWorkTabIfNecessary(); 258 } 259 } 260 } 261