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.APPS_VIEW_ITEM_MASK; 6 import static com.android.launcher3.LauncherState.OVERVIEW; 7 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; 8 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 9 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; 10 import static com.android.launcher3.anim.Interpolators.INSTANT; 11 import static com.android.launcher3.anim.Interpolators.LINEAR; 12 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; 13 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 14 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE; 15 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; 16 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.ObjectAnimator; 21 import android.content.Context; 22 import android.util.FloatProperty; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.view.animation.Interpolator; 26 import android.widget.EditText; 27 28 import com.android.launcher3.DeviceProfile; 29 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 30 import com.android.launcher3.Launcher; 31 import com.android.launcher3.LauncherState; 32 import com.android.launcher3.R; 33 import com.android.launcher3.anim.AnimationSuccessListener; 34 import com.android.launcher3.anim.PendingAnimation; 35 import com.android.launcher3.anim.PropertySetter; 36 import com.android.launcher3.statemanager.StateManager.StateHandler; 37 import com.android.launcher3.states.StateAnimationConfig; 38 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; 39 import com.android.launcher3.views.ScrimView; 40 import com.android.systemui.plugins.AllAppsSearchPlugin; 41 import com.android.systemui.plugins.PluginListener; 42 43 /** 44 * Handles AllApps view transition. 45 * 1) Slides all apps view using direct manipulation 46 * 2) When finger is released, animate to either top or bottom accordingly. 47 * <p/> 48 * Algorithm: 49 * If release velocity > THRES1, snap according to the direction of movement. 50 * If release velocity < THRES1, snap according to either top or bottom depending on whether it's 51 * closer to top or closer to the page indicator. 52 */ 53 public class AllAppsTransitionController implements StateHandler<LauncherState>, 54 OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> { 55 56 public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS = 57 new FloatProperty<AllAppsTransitionController>("allAppsProgress") { 58 59 @Override 60 public Float get(AllAppsTransitionController controller) { 61 return controller.mProgress; 62 } 63 64 @Override 65 public void setValue(AllAppsTransitionController controller, float progress) { 66 controller.setProgress(progress); 67 } 68 }; 69 70 private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0; 71 72 private AllAppsContainerView mAppsView; 73 private ScrimView mScrimView; 74 75 private final Launcher mLauncher; 76 private boolean mIsVerticalLayout; 77 78 // Animation in this class is controlled by a single variable {@link mProgress}. 79 // Visually, it represents top y coordinate of the all apps container if multiplied with 80 // {@link mShiftRange}. 81 82 // When {@link mProgress} is 0, all apps container is pulled up. 83 // When {@link mProgress} is 1, all apps container is pulled down. 84 private float mShiftRange; // changes depending on the orientation 85 private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent 86 87 private float mScrollRangeDelta = 0; 88 89 // plugin related variables 90 private AllAppsSearchPlugin mPlugin; 91 private View mPluginContent; 92 AllAppsTransitionController(Launcher l)93 public AllAppsTransitionController(Launcher l) { 94 mLauncher = l; 95 mShiftRange = mLauncher.getDeviceProfile().heightPx; 96 mProgress = 1f; 97 98 mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout(); 99 mLauncher.addOnDeviceProfileChangeListener(this); 100 } 101 getShiftRange()102 public float getShiftRange() { 103 return mShiftRange; 104 } 105 106 @Override onDeviceProfileChanged(DeviceProfile dp)107 public void onDeviceProfileChanged(DeviceProfile dp) { 108 mIsVerticalLayout = dp.isVerticalBarLayout(); 109 setScrollRangeDelta(mScrollRangeDelta); 110 111 if (mIsVerticalLayout) { 112 mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1); 113 mLauncher.getHotseat().setTranslationY(0); 114 mLauncher.getWorkspace().getPageIndicator().setTranslationY(0); 115 } 116 } 117 118 /** 119 * Note this method should not be called outside this class. This is public because it is used 120 * in xml-based animations which also handle updating the appropriate UI. 121 * 122 * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace 123 * 124 * @see #setState(LauncherState) 125 * @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation) 126 */ setProgress(float progress)127 public void setProgress(float progress) { 128 mProgress = progress; 129 mScrimView.setProgress(progress); 130 float shiftCurrent = progress * mShiftRange; 131 132 mAppsView.setTranslationY(shiftCurrent); 133 if (mPlugin != null) { 134 mPlugin.setProgress(progress); 135 } 136 } 137 getProgress()138 public float getProgress() { 139 return mProgress; 140 } 141 142 /** 143 * Sets the vertical transition progress to {@param state} and updates all the dependent UI 144 * accordingly. 145 */ 146 @Override setState(LauncherState state)147 public void setState(LauncherState state) { 148 setProgress(state.getVerticalProgress(mLauncher)); 149 setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER); 150 onProgressAnimationEnd(); 151 } 152 153 /** 154 * Creates an animation which updates the vertical transition progress and updates all the 155 * dependent UI using various animation events 156 */ 157 @Override setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation builder)158 public void setStateWithAnimation(LauncherState toState, 159 StateAnimationConfig config, PendingAnimation builder) { 160 float targetProgress = toState.getVerticalProgress(mLauncher); 161 if (Float.compare(mProgress, targetProgress) == 0) { 162 if (!config.onlyPlayAtomicComponent()) { 163 setAlphas(toState, config, builder); 164 } 165 // Fail fast 166 onProgressAnimationEnd(); 167 return; 168 } 169 170 if (config.onlyPlayAtomicComponent()) { 171 // There is no atomic component for the all apps transition, so just return early. 172 return; 173 } 174 175 Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW 176 ? config.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN) 177 : FAST_OUT_SLOW_IN; 178 179 Animator anim = createSpringAnimation(mProgress, targetProgress); 180 anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator)); 181 anim.addListener(getProgressAnimatorListener()); 182 builder.add(anim); 183 184 setAlphas(toState, config, builder); 185 } 186 createSpringAnimation(float... progressValues)187 public Animator createSpringAnimation(float... progressValues) { 188 return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues); 189 } 190 191 /** 192 * Updates the property for the provided state 193 */ setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter)194 public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) { 195 int visibleElements = state.getVisibleElements(mLauncher); 196 boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0; 197 boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0; 198 199 boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0; 200 201 Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR); 202 Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade); 203 204 if (mPlugin == null) { 205 setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade); 206 setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade); 207 mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, 208 hasAllAppsContent, setter, headerFade, allAppsFade); 209 } else { 210 setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade); 211 setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade); 212 setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade); 213 } 214 mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade); 215 216 setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA, 217 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade); 218 219 // Set visibility of the container at the very beginning or end of the transition. 220 setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, 221 hasAnyVisibleItem ? INSTANT : FINAL_FRAME); 222 } 223 getProgressAnimatorListener()224 public AnimatorListenerAdapter getProgressAnimatorListener() { 225 return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd); 226 } 227 setupViews(AllAppsContainerView appsView, ScrimView scrimView)228 public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) { 229 mAppsView = appsView; 230 mScrimView = scrimView; 231 PluginManagerWrapper.INSTANCE.get(mLauncher) 232 .addPluginListener(this, AllAppsSearchPlugin.class, false); 233 } 234 235 /** 236 * Updates the total scroll range but does not update the UI. 237 */ setScrollRangeDelta(float delta)238 void setScrollRangeDelta(float delta) { 239 mScrollRangeDelta = delta; 240 mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta; 241 242 if (mScrimView != null) { 243 mScrimView.reInitUi(); 244 } 245 } 246 247 /** 248 * Set the final view states based on the progress. 249 * TODO: This logic should go in {@link LauncherState} 250 */ onProgressAnimationEnd()251 private void onProgressAnimationEnd() { 252 if (Float.compare(mProgress, 1f) == 0) { 253 mAppsView.reset(false /* animate */); 254 } 255 updatePluginAnimationEnd(); 256 } 257 258 @Override onPluginConnected(AllAppsSearchPlugin plugin, Context context)259 public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) { 260 mPlugin = plugin; 261 mPluginContent = mLauncher.getLayoutInflater().inflate( 262 R.layout.all_apps_content_layout, mAppsView, false); 263 mAppsView.addView(mPluginContent); 264 mPluginContent.setAlpha(0f); 265 mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange); 266 } 267 268 @Override onPluginDisconnected(AllAppsSearchPlugin plugin)269 public void onPluginDisconnected(AllAppsSearchPlugin plugin) { 270 mPlugin = null; 271 mAppsView.removeView(mPluginContent); 272 } 273 onActivityDestroyed()274 public void onActivityDestroyed() { 275 PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this); 276 } 277 278 /** Used for the plugin to signal when drag starts happens 279 * @param toAllApps*/ onDragStart(boolean toAllApps)280 public void onDragStart(boolean toAllApps) { 281 if (mPlugin == null) return; 282 283 if (toAllApps) { 284 EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true); 285 mPlugin.setEditText(editText); 286 } 287 mPlugin.onDragStart(toAllApps ? 1f : 0f); 288 } 289 updatePluginAnimationEnd()290 private void updatePluginAnimationEnd() { 291 if (mPlugin == null) return; 292 mPlugin.onAnimationEnd(mProgress); 293 if (Float.compare(mProgress, 1f) == 0) { 294 mAppsView.getSearchUiManager().setTextSearchEnabled(false); 295 mPlugin.setEditText(null); 296 } 297 } 298 } 299