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

Commit c5797893 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Using edgeEffect for overscroll in all-apps and widget tray

Bug: 183966408
Test: Manual
Change-Id: Iea1b67504fc59fc7ce2ec657bf8ac7e3d8bfff1c
parent 78f5a8c6
Loading
Loading
Loading
Loading
+3 −25
Original line number Diff line number Diff line
@@ -77,7 +77,7 @@ import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePag
public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
        Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener {

    private static final float FLING_VELOCITY_MULTIPLIER = 135f;
    private static final float FLING_VELOCITY_MULTIPLIER = 1000 * .8f;
    // Starts the springs after at least 55% of the animation has passed.
    private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
    private static final int ALPHA_CHANNEL_COUNT = 2;
@@ -140,12 +140,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo

        mAllAppsStore.addUpdateListener(this::onAppsUpdated);

        addSpringView(R.id.all_apps_header);
        addSpringView(R.id.apps_list_view);
        addSpringView(R.id.all_apps_tabs_view_pager);

        mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);

    }

    /**
@@ -169,14 +164,6 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
        return mWorkModeSwitch;
    }


    @Override
    protected void setDampedScrollShift(float shift) {
        // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
        float maxShift = getSearchView().getHeight() / 2f;
        super.setDampedScrollShift(Utilities.boundToRange(shift, -maxShift, maxShift));
    }

    @Override
    public void onDeviceProfileChanged(DeviceProfile dp) {
        for (AdapterHolder holder : mAH) {
@@ -407,12 +394,6 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
        }
    }

    @Override
    public int getCanvasClipTopForOverscroll() {
        // Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped.
        return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop();
    }

    private void rebindAdapters(boolean showTabs) {
        rebindAdapters(showTabs, false /* force */);
    }
@@ -639,11 +620,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                if (shouldSpring
                        && valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
                    int searchViewId = getSearchView().getId();
                    addSpringView(searchViewId);
                    finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
                            (anim, canceled, value, velocity) -> removeSpringView(searchViewId));

                    absorbSwipeUpVelocity(Math.abs(
                            Math.round(velocity * FLING_VELOCITY_MULTIPLIER)));
                    shouldSpring = false;
                }
            }
+75 −104
Original line number Diff line number Diff line
@@ -15,51 +15,25 @@
 */
package com.android.launcher3.views;

import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.EdgeEffect;
import android.widget.RelativeLayout;

import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;

public class SpringRelativeLayout extends RelativeLayout {

    private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2;
    private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY;
    private static final float VELOCITY_MULTIPLIER = 0.3f;

    private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL =
            new FloatPropertyCompat<SpringRelativeLayout>("value") {

                @Override
                public float getValue(SpringRelativeLayout object) {
                    return object.mDampedScrollShift;
                }

                @Override
                public void setValue(SpringRelativeLayout object, float value) {
                    object.setDampedScrollShift(value);
                }
            };
import com.android.launcher3.Utilities;

    protected final SparseBooleanArray mSpringViews = new SparseBooleanArray();
    private final SpringAnimation mSpring;
/**
 * View group to allow rendering overscroll effect in a child at the parent level
 */
public class SpringRelativeLayout extends RelativeLayout {

    private float mDampedScrollShift = 0;
    private SpringEdgeEffect mActiveEdge;
    private final EdgeEffect mEdgeGlowTop;
    private final EdgeEffect mEdgeGlowBottom;

    public SpringRelativeLayout(Context context) {
        this(context, null);
@@ -71,98 +45,73 @@ public class SpringRelativeLayout extends RelativeLayout {

    public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0);
        mSpring.setSpring(new SpringForce(0)
                .setStiffness(STIFFNESS)
                .setDampingRatio(DAMPING_RATIO));
    }

    public void addSpringView(int id) {
        mSpringViews.put(id, true);
    }

    public void removeSpringView(int id) {
        mSpringViews.delete(id);
        invalidate();
    }

    /**
     * Used to clip the canvas when drawing child views during overscroll.
     */
    public int getCanvasClipTopForOverscroll() {
        return 0;
        mEdgeGlowTop = Utilities.ATLEAST_S
                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
        mEdgeGlowBottom = Utilities.ATLEAST_S
                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
        setWillNotDraw(false);
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
            int saveCount = canvas.save();

            canvas.clipRect(0, getCanvasClipTopForOverscroll(), getWidth(), getHeight());
            canvas.translate(0, mDampedScrollShift);
            boolean result = super.drawChild(canvas, child, drawingTime);

            canvas.restoreToCount(saveCount);

            return result;
        }
        return super.drawChild(canvas, child, drawingTime);
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (!mEdgeGlowTop.isFinished()) {
            final int restoreCount = canvas.save();
            canvas.translate(0, 0);
            mEdgeGlowTop.setSize(getWidth(), getHeight());
            if (mEdgeGlowTop.draw(canvas)) {
                postInvalidateOnAnimation();
            }

    private void setActiveEdge(SpringEdgeEffect edge) {
        if (mActiveEdge != edge && mActiveEdge != null) {
            mActiveEdge.mDistance = 0;
            canvas.restoreToCount(restoreCount);
        }
        mActiveEdge = edge;
        if (!mEdgeGlowBottom.isFinished()) {
            final int restoreCount = canvas.save();
            final int width = getWidth();
            final int height = getHeight();
            canvas.translate(-width, height);
            canvas.rotate(180, width, 0);
            mEdgeGlowBottom.setSize(width, height);
            if (mEdgeGlowBottom.draw(canvas)) {
                postInvalidateOnAnimation();
            }

    protected void setDampedScrollShift(float shift) {
        if (shift != mDampedScrollShift) {
            mDampedScrollShift = shift;
            invalidate();
            canvas.restoreToCount(restoreCount);
        }
    }

    private void finishScrollWithVelocity(float velocity) {
        mSpring.setStartVelocity(velocity);
        mSpring.setStartValue(mDampedScrollShift);
        mSpring.start();
    }

    protected void finishWithShiftAndVelocity(float shift, float velocity,
            DynamicAnimation.OnAnimationEndListener listener) {
        setDampedScrollShift(shift);
        mSpring.addEndListener(listener);
        finishScrollWithVelocity(velocity);
    /**
     * Absorbs the velocity as a result for swipe-up fling
     */
    protected void absorbSwipeUpVelocity(int velocity) {
        mEdgeGlowBottom.onAbsorb(velocity);
        invalidate();
    }

    public EdgeEffectFactory createEdgeEffectFactory() {
        return new SpringEdgeEffectFactory();
        return new ProxyEdgeEffectFactory();
    }

    private class SpringEdgeEffectFactory extends EdgeEffectFactory {
    private class ProxyEdgeEffectFactory extends EdgeEffectFactory {

        @NonNull @Override
        protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
            switch (direction) {
                case DIRECTION_TOP:
                    return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER);
                    return new EdgeEffectProxy(getContext(), mEdgeGlowTop);
                case DIRECTION_BOTTOM:
                    return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER);
                    return new EdgeEffectProxy(getContext(), mEdgeGlowBottom);
            }
            return super.createEdgeEffect(view, direction);
        }
    }

    private class SpringEdgeEffect extends EdgeEffect {
    private class EdgeEffectProxy extends EdgeEffect {

        private final float mVelocityMultiplier;
        private final EdgeEffect mParent;

        private float mDistance;

        public SpringEdgeEffect(Context context, float velocityMultiplier) {
        EdgeEffectProxy(Context context, EdgeEffect parent) {
            super(context);
            mVelocityMultiplier = velocityMultiplier;
            mParent = parent;
        }

        @Override
@@ -170,22 +119,44 @@ public class SpringRelativeLayout extends RelativeLayout {
            return false;
        }

        private void invalidateParentScrollEffect() {
            if (!mParent.isFinished()) {
                invalidate();
            }
        }

        @Override
        public void onAbsorb(int velocity) {
            finishScrollWithVelocity(velocity * mVelocityMultiplier);
            mParent.onAbsorb(velocity);
            invalidateParentScrollEffect();
        }

        @Override
        public void onPull(float deltaDistance) {
            mParent.onPull(deltaDistance);
            invalidateParentScrollEffect();
        }

        @Override
        public void onPull(float deltaDistance, float displacement) {
            setActiveEdge(this);
            mDistance += deltaDistance * (mVelocityMultiplier / 3f);
            setDampedScrollShift(mDistance * getHeight());
            mParent.onPull(deltaDistance, displacement);
            invalidateParentScrollEffect();
        }

        @Override
        public void onRelease() {
            mDistance = 0;
            finishScrollWithVelocity(0);
            mParent.onRelease();
            invalidateParentScrollEffect();
        }

        @Override
        public void finish() {
            mParent.finish();
        }

        @Override
        public boolean isFinished() {
            return mParent.isFinished();
        }
    }
}
 No newline at end of file
+0 −5
Original line number Diff line number Diff line
@@ -144,17 +144,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
            findViewById(R.id.tab_work)
                    .setOnClickListener((View view) -> mViewPager.snapToPage(1));
            fastScroller.setIsRecyclerViewFirstChildInParent(false);
            springLayout.addSpringView(R.id.primary_widgets_list_view);
            springLayout.addSpringView(R.id.work_widgets_list_view);
        } else {
            mViewPager = null;
            springLayout.addSpringView(R.id.primary_widgets_list_view);
        }

        layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
                true);
        springLayout.addSpringView(R.id.search_and_recommendations_container);

        mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
                findViewById(R.id.search_and_recommendations_container));
        mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(