| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.launcher3.allapps; |
| |
| import static android.view.View.VISIBLE; |
| |
| import static com.android.app.animation.Interpolators.DECELERATE_1_7; |
| import static com.android.app.animation.Interpolators.INSTANT; |
| import static com.android.app.animation.Interpolators.clampToProgress; |
| import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; |
| import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; |
| |
| import android.animation.ObjectAnimator; |
| import android.animation.TimeInterpolator; |
| import android.view.View; |
| import android.view.animation.Interpolator; |
| |
| import com.android.launcher3.R; |
| |
| /** Coordinates the transition between Search and A-Z in All Apps. */ |
| public class SearchTransitionController extends RecyclerViewAnimationController { |
| |
| // Interpolator when the user taps the QSB while already in All Apps. |
| private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DECELERATE_1_7; |
| // Interpolator when the user taps the QSB from home screen, so transition to all apps is |
| // happening simultaneously. |
| private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT; |
| |
| private boolean mSkipNextAnimationWithinAllApps; |
| |
| public SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView) { |
| super(allAppsContainerView); |
| } |
| |
| /** |
| * Starts the transition to or from search state. If a transition is already in progress, the |
| * animation will start from that point with the new duration, and the previous onEndRunnable |
| * will not be called. |
| * |
| * @param goingToSearch true if will be showing search results, otherwise will be showing a-z |
| * @param duration time in ms for the animation to run |
| * @param onEndRunnable will be called when the animation finishes, unless another animation is |
| * scheduled in the meantime |
| */ |
| @Override |
| protected void animateToState(boolean goingToSearch, long duration, Runnable onEndRunnable) { |
| super.animateToState(goingToSearch, duration, onEndRunnable); |
| if (!goingToSearch) { |
| mAnimator.addListener(forSuccessCallback(() -> { |
| mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(false); |
| mAllAppsContainerView.getFloatingHeaderView().reset(false /* animate */); |
| mAllAppsContainerView.getAppsRecyclerViewContainer().setTranslationY(0); |
| })); |
| } |
| mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true); |
| mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE); |
| mAllAppsContainerView.getFloatingHeaderView().maybeSetTabVisibility(VISIBLE); |
| mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE); |
| getRecyclerView().setVisibility(VISIBLE); |
| } |
| |
| @Override |
| protected SearchRecyclerView getRecyclerView() { |
| return mAllAppsContainerView.getSearchRecyclerView(); |
| } |
| |
| @Override |
| protected int onProgressUpdated(float searchToAzProgress) { |
| int searchHeight = super.onProgressUpdated(searchToAzProgress); |
| |
| FloatingHeaderView headerView = mAllAppsContainerView.getFloatingHeaderView(); |
| |
| // Add predictions + app divider height to account for predicted apps which will now be in |
| // the Search RV instead of the floating header view. Note `getFloatingRowsHeight` returns 0 |
| // when predictions are not shown. |
| int appsTranslationY = searchHeight + headerView.getFloatingRowsHeight(); |
| |
| if (headerView.usingTabs()) { |
| // Move tabs below the search results, and fade them out in 20% of the animation. |
| headerView.setTranslationY(searchHeight); |
| headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f)); |
| |
| // Account for the additional padding added for the tabs. |
| appsTranslationY += |
| headerView.getTabsAdditionalPaddingBottom() |
| + mAllAppsContainerView.getResources().getDimensionPixelOffset( |
| R.dimen.all_apps_tabs_margin_top) |
| - headerView.getPaddingTop(); |
| } |
| |
| View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer(); |
| appsContainer.setTranslationY(appsTranslationY); |
| // Fade apps out with tabs (in 20% of the total animation). |
| appsContainer.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f)); |
| return searchHeight; |
| } |
| |
| /** |
| * Should only animate if the view is not an app icon or if the app row is complete. |
| */ |
| @Override |
| protected boolean shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete) { |
| return !isAppIcon(view) || appRowComplete; |
| } |
| |
| @Override |
| protected TimeInterpolator getInterpolator() { |
| TimeInterpolator timeInterpolator = |
| mAllAppsContainerView.isInAllApps() |
| ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS; |
| if (mSkipNextAnimationWithinAllApps) { |
| timeInterpolator = INSTANT; |
| mSkipNextAnimationWithinAllApps = false; |
| } |
| return timeInterpolator; |
| } |
| |
| /** |
| * This should only be called from {@code LauncherSearchSessionManager} when app restarts due to |
| * theme changes. |
| */ |
| public void setSkipAnimationWithinAllApps(boolean skip) { |
| mSkipNextAnimationWithinAllApps = skip; |
| } |
| } |