Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73d44f63 authored by Steven Ng's avatar Steven Ng
Browse files

Animate recommended widgets vertical transition when reset widgets picker scroll position

Test: Set up work profile. Then open the widgets picker, scroll down from
      the personal tab, then switch tab. Observes a vertical transition
      animation of the search and recommendation container view.
Bug: 188538428
Change-Id: I2b8523d1682a6312de3b5393c0bff782ae110b3c
parent a79cd201
Loading
Loading
Loading
Loading
+49 −23
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.launcher3.widget.picker;

import android.animation.ValueAnimator;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.View;
@@ -32,13 +33,14 @@ import com.android.launcher3.workprofile.PersonalWorkPagedView;
 * vertical displacement upon scrolling.
 */
final class SearchAndRecommendationsScrollController implements
        RecyclerViewFastScroller.OnFastScrollChangeListener {
        RecyclerViewFastScroller.OnFastScrollChangeListener, ValueAnimator.AnimatorUpdateListener {
    private final boolean mHasWorkProfile;
    private final SearchAndRecommendationViewHolder mViewHolder;
    private final View mSearchAndRecommendationViewParent;
    private final WidgetsRecyclerView mPrimaryRecyclerView;
    private final WidgetsRecyclerView mSearchRecyclerView;
    private final int mTabsHeight;
    private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
    private final Point mTempOffset = new Point();

    // The following are only non null if mHasWorkProfile is true.
@@ -47,8 +49,9 @@ final class SearchAndRecommendationsScrollController implements
    @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;

    private WidgetsRecyclerView mCurrentRecyclerView;
    private int mCurrentRecyclerViewScrollY = 0;

    private OnContentChangeListener mOnContentChangeListener = () -> applyVerticalTransition();
    private OnContentChangeListener mOnContentChangeListener = () -> onScrollChanged();

    /**
     * The vertical distance, in pixels, until the search is pinned at the top of the screen when
@@ -89,23 +92,25 @@ final class SearchAndRecommendationsScrollController implements
        mPrimaryWorkTabsView = personalWorkTabsView;
        mPrimaryWorkViewPager = primaryWorkViewPager;
        mTabsHeight = tabsHeight;
        setCurrentRecyclerView(mPrimaryRecyclerView);
        setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false);
    }

    /** Sets the current active {@link WidgetsRecyclerView}. */
    public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
        setCurrentRecyclerView(currentRecyclerView, /* animateReset= */ true);
    }

    /** Sets the current active {@link WidgetsRecyclerView}. */
    private void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView,
            boolean animateReset) {
        if (mCurrentRecyclerView == currentRecyclerView) {
            return;
        }
        if (mCurrentRecyclerView != null) {
            mCurrentRecyclerView.setOnContentChangeListener(null);
        }
        mCurrentRecyclerView = currentRecyclerView;
        mCurrentRecyclerView.setOnContentChangeListener(mOnContentChangeListener);
        mViewHolder.mHeaderTitle.setTranslationY(0);
        mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
        mViewHolder.mSearchBarContainer.setTranslationY(0);

        if (mHasWorkProfile) {
            mPrimaryWorkTabsView.setTranslationY(0);
        }
        reset(animateReset);
    }

    /**
@@ -222,6 +227,12 @@ final class SearchAndRecommendationsScrollController implements

    @Override
    public void onScrollChanged() {
        int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
        if (recyclerViewYOffset < 0) return;
        mCurrentRecyclerViewScrollY = recyclerViewYOffset;
        if (mAnimator.isStarted()) {
            mAnimator.cancel();
        }
        applyVerticalTransition();
    }

@@ -230,34 +241,43 @@ final class SearchAndRecommendationsScrollController implements
     * views (e.g. recycler views, tabs) upon scrolling / content changes in the recycler view.
     */
    private void applyVerticalTransition() {
        // Always use the recycler view offset because fast scroller offset has a different scale.
        int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
        if (recyclerViewYOffset < 0) return;

        if (mCollapsibleHeightForRecommendation > 0) {
            int yDisplacement = Math.max(-recyclerViewYOffset,
            int yDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
                    -mCollapsibleHeightForRecommendation);
            mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
            mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
        }

        if (mCollapsibleHeightForSearch > 0) {
            int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
            int searchYDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
                    -mCollapsibleHeightForSearch);
            mViewHolder.mSearchBarContainer.setTranslationY(searchYDisplacement);
        }

        if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
            int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
            int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY,
                    -mCollapsibleHeightForTabs);
            mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
        }
    }

    /** Resets any previous view translation. */
    public void reset() {
        mViewHolder.mHeaderTitle.setTranslationY(0);
        mViewHolder.mSearchBarContainer.setTranslationY(0);
        if (mHasWorkProfile) {
            mPrimaryWorkTabsView.setTranslationY(0);
    public void reset(boolean animate) {
        if (mCurrentRecyclerViewScrollY == 0) {
            return;
        }
        if (mAnimator.isStarted()) {
            mAnimator.cancel();
        }

        if (animate) {
            mAnimator.setIntValues(mCurrentRecyclerViewScrollY, 0);
            mAnimator.addUpdateListener(this);
            mAnimator.setDuration(300);
            mAnimator.start();
        } else {
            mCurrentRecyclerViewScrollY = 0;
            applyVerticalTransition();
        }
    }

@@ -308,6 +328,12 @@ final class SearchAndRecommendationsScrollController implements
                + marginLayoutParams.topMargin;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mCurrentRecyclerViewScrollY = (Integer) animation.getAnimatedValue();
        applyVerticalTransition();
    }

    /**
     * A listener to be notified when there is a content change in the recycler view that may affect
     * the relative position of the search and recommendation container.
+1 −1
Original line number Diff line number Diff line
@@ -248,7 +248,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
            mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
        }
        mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
        mSearchAndRecommendationsScrollController.reset();
        mSearchAndRecommendationsScrollController.reset(/* animate= */ true);
    }

    @VisibleForTesting