Merge "Using edgeEffect for overscroll in all-apps and widget tray" into sc-dev

This commit is contained in:
Sunny Goyal
2021-03-30 17:13:04 +00:00
committed by Android (Google) Code Review
3 changed files with 78 additions and 134 deletions
@@ -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) {
@@ -408,12 +395,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 */);
}
@@ -640,11 +621,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;
}
}
@@ -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;
import com.android.launcher3.Utilities;
/**
* View group to allow rendering overscroll effect in a child at the parent level
*/
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);
}
};
protected final SparseBooleanArray mSpringViews = new SparseBooleanArray();
private final SpringAnimation mSpring;
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;
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();
}
canvas.restoreToCount(restoreCount);
}
return super.drawChild(canvas, child, drawingTime);
}
private void setActiveEdge(SpringEdgeEffect edge) {
if (mActiveEdge != edge && mActiveEdge != null) {
mActiveEdge.mDistance = 0;
}
mActiveEdge = edge;
}
protected void setDampedScrollShift(float shift) {
if (shift != mDampedScrollShift) {
mDampedScrollShift = shift;
invalidate();
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();
}
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();
}
}
}
@@ -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(