• 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;
5 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
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.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
9 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
10 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
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.util.SystemUiController.UI_STATE_ALL_APPS;
14 
15 import android.animation.Animator;
16 import android.animation.AnimatorListenerAdapter;
17 import android.animation.ObjectAnimator;
18 import android.util.Property;
19 import android.view.View;
20 import android.view.animation.Interpolator;
21 
22 import com.android.launcher3.DeviceProfile;
23 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
24 import com.android.launcher3.Launcher;
25 import com.android.launcher3.LauncherState;
26 import com.android.launcher3.LauncherStateManager.AnimationConfig;
27 import com.android.launcher3.LauncherStateManager.StateHandler;
28 import com.android.launcher3.R;
29 import com.android.launcher3.anim.AnimationSuccessListener;
30 import com.android.launcher3.anim.AnimatorSetBuilder;
31 import com.android.launcher3.anim.PropertySetter;
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     public static final Property<AllAppsTransitionController, Float> ALL_APPS_PROGRESS =
48             new Property<AllAppsTransitionController, Float>(Float.class, "allAppsProgress") {
49 
50         @Override
51         public Float get(AllAppsTransitionController controller) {
52             return controller.mProgress;
53         }
54 
55         @Override
56         public void set(AllAppsTransitionController controller, Float progress) {
57             controller.setProgress(progress);
58         }
59     };
60 
61     private AllAppsContainerView mAppsView;
62     private ScrimView mScrimView;
63 
64     private final Launcher mLauncher;
65     private final boolean mIsDarkTheme;
66     private boolean mIsVerticalLayout;
67 
68     // Animation in this class is controlled by a single variable {@link mProgress}.
69     // Visually, it represents top y coordinate of the all apps container if multiplied with
70     // {@link mShiftRange}.
71 
72     // When {@link mProgress} is 0, all apps container is pulled up.
73     // When {@link mProgress} is 1, all apps container is pulled down.
74     private float mShiftRange;      // changes depending on the orientation
75     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
76 
77     private float mScrollRangeDelta = 0;
78 
AllAppsTransitionController(Launcher l)79     public AllAppsTransitionController(Launcher l) {
80         mLauncher = l;
81         mShiftRange = mLauncher.getDeviceProfile().heightPx;
82         mProgress = 1f;
83 
84         mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
85         mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
86         mLauncher.addOnDeviceProfileChangeListener(this);
87     }
88 
getShiftRange()89     public float getShiftRange() {
90         return mShiftRange;
91     }
92 
onProgressAnimationStart()93     private void onProgressAnimationStart() {
94         // Initialize values that should not change until #onDragEnd
95         mAppsView.setVisibility(View.VISIBLE);
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.setAlpha(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         float hotseatTranslation = -mShiftRange + shiftCurrent;
126 
127         if (!mIsVerticalLayout) {
128             mLauncher.getHotseat().setTranslationY(hotseatTranslation);
129             mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
130         }
131 
132         // Use a light system UI (dark icons) if all apps is behind at least half of the
133         // status bar.
134         boolean forceChange = shiftCurrent - mScrimView.getDragHandleSize()
135                 <= mLauncher.getDeviceProfile().getInsets().top / 2;
136         if (forceChange) {
137             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
138         } else {
139             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
140         }
141     }
142 
getProgress()143     public float getProgress() {
144         return mProgress;
145     }
146 
147     /**
148      * Sets the vertical transition progress to {@param state} and updates all the dependent UI
149      * accordingly.
150      */
151     @Override
setState(LauncherState state)152     public void setState(LauncherState state) {
153         setProgress(state.getVerticalProgress(mLauncher));
154         setAlphas(state, NO_ANIM_PROPERTY_SETTER);
155         onProgressAnimationEnd();
156     }
157 
158     /**
159      * Creates an animation which updates the vertical transition progress and updates all the
160      * dependent UI using various animation events
161      */
162     @Override
setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder, AnimationConfig config)163     public void setStateWithAnimation(LauncherState toState,
164             AnimatorSetBuilder builder, AnimationConfig config) {
165         float targetProgress = toState.getVerticalProgress(mLauncher);
166         if (Float.compare(mProgress, targetProgress) == 0) {
167             setAlphas(toState, config.getPropertySetter(builder));
168             // Fail fast
169             onProgressAnimationEnd();
170             return;
171         }
172 
173         if (!config.playNonAtomicComponent()) {
174             // There is no atomic component for the all apps transition, so just return early.
175             return;
176         }
177 
178         Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
179                 ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
180                 : FAST_OUT_SLOW_IN;
181         ObjectAnimator anim =
182                 ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
183         anim.setDuration(config.duration);
184         anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
185         anim.addListener(getProgressAnimatorListener());
186 
187         builder.play(anim);
188 
189         setAlphas(toState, config.getPropertySetter(builder));
190     }
191 
setAlphas(LauncherState toState, PropertySetter setter)192     private void setAlphas(LauncherState toState, PropertySetter setter) {
193         int visibleElements = toState.getVisibleElements(mLauncher);
194         boolean hasHeader = (visibleElements & ALL_APPS_HEADER) != 0;
195         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
196         boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
197 
198         setter.setViewAlpha(mAppsView.getSearchView(), hasHeader ? 1 : 0, LINEAR);
199         setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, LINEAR);
200         setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, LINEAR);
201         mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasContent, setter);
202 
203         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
204                 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, LINEAR);
205     }
206 
getProgressAnimatorListener()207     public AnimatorListenerAdapter getProgressAnimatorListener() {
208         return new AnimationSuccessListener() {
209             @Override
210             public void onAnimationSuccess(Animator animator) {
211                 onProgressAnimationEnd();
212             }
213 
214             @Override
215             public void onAnimationStart(Animator animation) {
216                 onProgressAnimationStart();
217             }
218         };
219     }
220 
221     public void setupViews(AllAppsContainerView appsView) {
222         mAppsView = appsView;
223         mScrimView = mLauncher.findViewById(R.id.scrim_view);
224     }
225 
226     /**
227      * Updates the total scroll range but does not update the UI.
228      */
229     public void setScrollRangeDelta(float delta) {
230         mScrollRangeDelta = delta;
231         mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta;
232 
233         if (mScrimView != null) {
234             mScrimView.reInitUi();
235         }
236     }
237 
238     /**
239      * Set the final view states based on the progress.
240      * TODO: This logic should go in {@link LauncherState}
241      */
242     private void onProgressAnimationEnd() {
243         if (Float.compare(mProgress, 1f) == 0) {
244             mAppsView.setVisibility(View.INVISIBLE);
245             mAppsView.reset(false /* animate */);
246         } else if (Float.compare(mProgress, 0f) == 0) {
247             mAppsView.setVisibility(View.VISIBLE);
248             mAppsView.onScrollUpEnd();
249         } else {
250             mAppsView.setVisibility(View.VISIBLE);
251         }
252     }
253 }
254