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