1 /* 2 * Copyright (C) 2022 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 17 package com.android.launcher3.allapps; 18 19 import static android.view.View.VISIBLE; 20 21 import static com.android.app.animation.Interpolators.DECELERATE_1_7; 22 import static com.android.app.animation.Interpolators.INSTANT; 23 import static com.android.app.animation.Interpolators.clampToProgress; 24 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 25 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; 26 27 import android.animation.ObjectAnimator; 28 import android.animation.TimeInterpolator; 29 import android.view.View; 30 import android.view.animation.Interpolator; 31 32 import com.android.launcher3.R; 33 34 /** Coordinates the transition between Search and A-Z in All Apps. */ 35 public class SearchTransitionController extends RecyclerViewAnimationController { 36 37 // Interpolator when the user taps the QSB while already in All Apps. 38 private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DECELERATE_1_7; 39 // Interpolator when the user taps the QSB from home screen, so transition to all apps is 40 // happening simultaneously. 41 private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT; 42 43 private boolean mSkipNextAnimationWithinAllApps; 44 SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView)45 public SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView) { 46 super(allAppsContainerView); 47 } 48 49 /** 50 * Starts the transition to or from search state. If a transition is already in progress, the 51 * animation will start from that point with the new duration, and the previous onEndRunnable 52 * will not be called. 53 * 54 * @param goingToSearch true if will be showing search results, otherwise will be showing a-z 55 * @param duration time in ms for the animation to run 56 * @param onEndRunnable will be called when the animation finishes, unless another animation is 57 * scheduled in the meantime 58 */ 59 @Override animateToState(boolean goingToSearch, long duration, Runnable onEndRunnable)60 protected void animateToState(boolean goingToSearch, long duration, Runnable onEndRunnable) { 61 super.animateToState(goingToSearch, duration, onEndRunnable); 62 if (!goingToSearch) { 63 mAnimator.addListener(forSuccessCallback(() -> { 64 mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(false); 65 mAllAppsContainerView.getFloatingHeaderView().reset(false /* animate */); 66 mAllAppsContainerView.getAppsRecyclerViewContainer().setTranslationY(0); 67 })); 68 } 69 mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true); 70 mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE); 71 mAllAppsContainerView.getFloatingHeaderView().maybeSetTabVisibility(VISIBLE); 72 mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE); 73 getRecyclerView().setVisibility(VISIBLE); 74 } 75 76 @Override getRecyclerView()77 protected SearchRecyclerView getRecyclerView() { 78 return mAllAppsContainerView.getSearchRecyclerView(); 79 } 80 81 @Override onProgressUpdated(float searchToAzProgress)82 protected int onProgressUpdated(float searchToAzProgress) { 83 int searchHeight = super.onProgressUpdated(searchToAzProgress); 84 85 FloatingHeaderView headerView = mAllAppsContainerView.getFloatingHeaderView(); 86 87 // Add predictions + app divider height to account for predicted apps which will now be in 88 // the Search RV instead of the floating header view. Note `getFloatingRowsHeight` returns 0 89 // when predictions are not shown. 90 int appsTranslationY = searchHeight + headerView.getFloatingRowsHeight(); 91 92 if (headerView.usingTabs()) { 93 // Move tabs below the search results, and fade them out in 20% of the animation. 94 headerView.setTranslationY(searchHeight); 95 headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f)); 96 97 // Account for the additional padding added for the tabs. 98 appsTranslationY += 99 headerView.getTabsAdditionalPaddingBottom() 100 + mAllAppsContainerView.getResources().getDimensionPixelOffset( 101 R.dimen.all_apps_tabs_margin_top) 102 - headerView.getPaddingTop(); 103 } 104 105 View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer(); 106 appsContainer.setTranslationY(appsTranslationY); 107 // Fade apps out with tabs (in 20% of the total animation). 108 appsContainer.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f)); 109 return searchHeight; 110 } 111 112 /** 113 * Should only animate if the view is not an app icon or if the app row is complete. 114 */ 115 @Override shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete)116 protected boolean shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete) { 117 return !isAppIcon(view) || appRowComplete; 118 } 119 120 @Override getInterpolator()121 protected TimeInterpolator getInterpolator() { 122 TimeInterpolator timeInterpolator = 123 mAllAppsContainerView.isInAllApps() 124 ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS; 125 if (mSkipNextAnimationWithinAllApps) { 126 timeInterpolator = INSTANT; 127 mSkipNextAnimationWithinAllApps = false; 128 } 129 return timeInterpolator; 130 } 131 132 /** 133 * This should only be called from {@code LauncherSearchSessionManager} when app restarts due to 134 * theme changes. 135 */ setSkipAnimationWithinAllApps(boolean skip)136 public void setSkipAnimationWithinAllApps(boolean skip) { 137 mSkipNextAnimationWithinAllApps = skip; 138 } 139 } 140