Merge "Improved the behavior of overscroll" into sc-dev

This commit is contained in:
Selim Cinek
2021-06-07 18:12:40 +00:00
committed by Android (Google) Code Review
11 changed files with 220 additions and 153 deletions

View File

@@ -311,6 +311,13 @@ public class FlingAnimationUtils {
return mMinVelocityPxPerSecond; return mMinVelocityPxPerSecond;
} }
/**
* @return a velocity considered fast
*/
public float getHighVelocityPxPerSecond() {
return mHighVelocityPxPerSecond;
}
/** /**
* An interpolator which interpolates two interpolators with an interpolator. * An interpolator which interpolates two interpolators with an interpolator.
*/ */

View File

@@ -93,6 +93,16 @@ public class Interpolators {
(float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f)); (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
} }
/**
* Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
* starts immediately here, instead of first having a section of non-overshooting
*
* @param progress a progress value going from 0 to 1
*/
public static float getOvershootInterpolation(float progress) {
return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
}
/** /**
* Interpolate alpha for notifications background scrim during shade expansion. * Interpolate alpha for notifications background scrim during shade expansion.
* @param fraction Shade expansion fraction * @param fraction Shade expansion fraction

View File

@@ -771,6 +771,9 @@
<!-- Move distance for the unlock hint animation on the lockscreen --> <!-- Move distance for the unlock hint animation on the lockscreen -->
<dimen name="hint_move_distance">75dp</dimen> <dimen name="hint_move_distance">75dp</dimen>
<!-- The overshoot amount when the panel flings open -->
<dimen name="panel_overshoot_amount">16dp</dimen>
<!-- The width of the region on the left/right edge of the screen for performing the camera/ <!-- The width of the region on the left/right edge of the screen for performing the camera/
phone hints. --> phone hints. -->
<dimen name="edge_tap_area_width">48dp</dimen> <dimen name="edge_tap_area_width">48dp</dimen>

View File

@@ -83,7 +83,7 @@ public class AmbientState {
private ExpandableNotificationRow mTrackedHeadsUpRow; private ExpandableNotificationRow mTrackedHeadsUpRow;
private float mAppearFraction; private float mAppearFraction;
private boolean mIsShadeOpening; private boolean mIsShadeOpening;
private float mSectionPadding; private float mOverExpansion;
/** Distance of top of notifications panel from top of screen. */ /** Distance of top of notifications panel from top of screen. */
private float mStackY = 0; private float mStackY = 0;
@@ -182,12 +182,12 @@ public class AmbientState {
return mIsShadeOpening; return mIsShadeOpening;
} }
void setSectionPadding(float padding) { void setOverExpansion(float overExpansion) {
mSectionPadding = padding; mOverExpansion = overExpansion;
} }
float getSectionPadding() { float getOverExpansion() {
return mSectionPadding; return mOverExpansion;
} }
private static int getZDistanceBetweenElements(Context context) { private static int getZDistanceBetweenElements(Context context) {

View File

@@ -119,6 +119,7 @@ import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -470,6 +471,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} }
}; };
private Consumer<Integer> mScrollListener;
private final ScrollAdapter mScrollAdapter = new ScrollAdapter() { private final ScrollAdapter mScrollAdapter = new ScrollAdapter() {
@Override @Override
public boolean isScrolledToTop() { public boolean isScrolledToTop() {
@@ -552,8 +554,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} }
} }
void setSectionPadding(float margin) { /**
mAmbientState.setSectionPadding(margin); * Set the overexpansion of the panel to be applied to the view.
*/
void setOverExpansion(float margin) {
mAmbientState.setOverExpansion(margin);
updateStackPosition();
requestChildrenUpdate(); requestChildrenUpdate();
} }
@@ -1136,7 +1142,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/ */
private void updateStackPosition() { private void updateStackPosition() {
// Consider interpolating from an mExpansionStartY for use on lockscreen and AOD // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition; float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ mAmbientState.getOverExpansion();
final float fraction = mAmbientState.getExpansionFraction(); final float fraction = mAmbientState.getExpansionFraction();
final float stackY = MathUtils.lerp(0, endTopPosition, fraction); final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
mAmbientState.setStackY(stackY); mAmbientState.setStackY(stackY);
@@ -1144,7 +1151,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mOnStackYChanged.run(); mOnStackYChanged.run();
} }
if (mQsExpansionFraction <= 0) { if (mQsExpansionFraction <= 0) {
final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mSidePaddings;
final float stackEndHeight = Math.max(0f, final float stackEndHeight = Math.max(0f,
getHeight() - getEmptyBottomMargin() - mTopPadding); getHeight() - getEmptyBottomMargin() - mTopPadding);
mAmbientState.setStackEndHeight(stackEndHeight); mAmbientState.setStackEndHeight(stackEndHeight);
@@ -1166,7 +1172,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.COORDINATOR) @ShadeViewRefactor(RefactorComponent.COORDINATOR)
public void setExpandedHeight(float height) { public void setExpandedHeight(float height) {
final float shadeBottom = getHeight() - getEmptyBottomMargin(); final float shadeBottom = getHeight() - getEmptyBottomMargin();
final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f); final float expansionFraction = MathUtils.saturate(height / shadeBottom);
mAmbientState.setExpansionFraction(expansionFraction); mAmbientState.setExpansionFraction(expansionFraction);
updateStackPosition(); updateStackPosition();
@@ -2395,8 +2401,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
float topOverScroll = getCurrentOverScrollAmount(true); float topOverScroll = getCurrentOverScrollAmount(true);
return mScrolledToTopOnFirstDown return mScrolledToTopOnFirstDown
&& !mExpandedInThisMotion && !mExpandedInThisMotion
&& topOverScroll > mMinTopOverScrollToEscape && (initialVelocity > mMinimumVelocity
&& initialVelocity > 0; || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
} }
/** /**
@@ -4552,6 +4558,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} }
private void updateOnScrollChange() { private void updateOnScrollChange() {
if (mScrollListener != null) {
mScrollListener.accept(mOwnScrollY);
}
updateForwardAndBackwardScrollability(); updateForwardAndBackwardScrollability();
requestChildrenUpdate(); requestChildrenUpdate();
} }
@@ -5162,6 +5171,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
requestChildrenUpdate(); requestChildrenUpdate();
} }
/**
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
mScrollListener = listener;
}
/** /**
* A listener that is notified when the empty space below the notifications is clicked on * A listener that is notified when the empty space below the notifications is clicked on
*/ */

View File

@@ -124,6 +124,7 @@ import com.android.systemui.tuner.TunerService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -302,8 +303,11 @@ public class NotificationStackScrollLayoutController {
} }
}; };
public void setSectionPadding(float padding) { /**
mView.setSectionPadding(padding); * Set the overexpansion of the panel to be applied to the view.
*/
public void setOverExpansion(float overExpansion) {
mView.setOverExpansion(overExpansion);
} }
private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
@@ -944,10 +948,6 @@ public class NotificationStackScrollLayoutController {
mView.setOverScrollAmount(amount, onTop, animate); mView.setOverScrollAmount(amount, onTop, animate);
} }
public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
mView.setOverScrolledPixels(numPixels, onTop, animate);
}
public void resetScrollPosition() { public void resetScrollPosition() {
mView.resetScrollPosition(); mView.resetScrollPosition();
} }
@@ -1054,10 +1054,6 @@ public class NotificationStackScrollLayoutController {
return mView.getCurrentOverScrollAmount(top); return mView.getCurrentOverScrollAmount(top);
} }
public float getCurrentOverScrolledPixels(boolean top) {
return mView.getCurrentOverScrolledPixels(top);
}
public float calculateAppearFraction(float height) { public float calculateAppearFraction(float height) {
return mView.calculateAppearFraction(height); return mView.calculateAppearFraction(height);
} }
@@ -1430,6 +1426,13 @@ public class NotificationStackScrollLayoutController {
mView.setExtraTopInsetForFullShadeTransition(extraTopInset); mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
} }
/**
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
mView.setOnScrollListener(listener);
}
/** /**
* Enum for UiEvent logged from this class * Enum for UiEvent logged from this class
*/ */

View File

@@ -403,10 +403,6 @@ public class StackScrollAlgorithm {
} }
viewState.yTranslation = algorithmState.mCurrentYPosition; viewState.yTranslation = algorithmState.mCurrentYPosition;
if (view instanceof SectionHeaderView) {
// Add padding before sections for overscroll effect.
viewState.yTranslation += expansionFraction * ambientState.getSectionPadding();
}
if (view instanceof FooterView) { if (view instanceof FooterView) {
final boolean shadeClosed = !ambientState.isShadeExpanded(); final boolean shadeClosed = !ambientState.isShadeExpanded();

View File

@@ -510,7 +510,6 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mShowingKeyguardHeadsUp; private boolean mShowingKeyguardHeadsUp;
private boolean mAllowExpandForSmallExpansion; private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable; private Runnable mExpandAfterLayoutRunnable;
private float mSectionPadding;
/** /**
* The padding between the start of notifications and the qs boundary on the lockscreen. * The padding between the start of notifications and the qs boundary on the lockscreen.
@@ -589,7 +588,6 @@ public class NotificationPanelViewController extends PanelViewController {
private NotificationShelfController mNotificationShelfController; private NotificationShelfController mNotificationShelfController;
private int mScrimCornerRadius; private int mScrimCornerRadius;
private int mScreenCornerRadius; private int mScreenCornerRadius;
private int mNotificationScrimPadding;
private boolean mQSAnimatingHiddenFromCollapsed; private boolean mQSAnimatingHiddenFromCollapsed;
private final Executor mUiExecutor; private final Executor mUiExecutor;
@@ -824,6 +822,7 @@ public class NotificationPanelViewController extends PanelViewController {
mOnHeightChangedListener); mOnHeightChangedListener);
mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
mOnOverscrollTopChangedListener); mOnOverscrollTopChangedListener);
mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener); mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
@@ -908,8 +907,6 @@ public class NotificationPanelViewController extends PanelViewController {
mScrimCornerRadius = mResources.getDimensionPixelSize( mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius); R.dimen.notification_scrim_corner_radius);
mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources); mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources);
mNotificationScrimPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings); R.dimen.notification_side_paddings);
} }
@@ -2190,6 +2187,22 @@ public class NotificationPanelViewController extends PanelViewController {
} }
}; };
private void onNotificationScrolled(int newScrollPosition) {
// Since this is an overscroller, sometimes the scrollY can be temporarily negative
// (when overscrollng on the top and flinging). Let's
updateQSExpansionEnabled();
}
@Override
public void setIsShadeOpening(boolean opening) {
mAmbientState.setIsShadeOpening(opening);
updateQSExpansionEnabled();
}
private void updateQSExpansionEnabled() {
setQsExpansionEnabled(mAmbientState.getScrollY() <= 0 && !mAmbientState.isShadeOpening());
}
/** /**
* Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade
* and QS state. * and QS state.
@@ -2203,7 +2216,6 @@ public class NotificationPanelViewController extends PanelViewController {
final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction()); final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0) final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0)
&& !mShouldUseSplitNotificationShade; && !mShouldUseSplitNotificationShade;
setQsExpansionEnabled(mAmbientState.getScrollY() == 0 && !mAmbientState.isShadeOpening());
if (!mShouldUseSplitNotificationShade) { if (!mShouldUseSplitNotificationShade) {
if (mTransitioningToFullShadeProgress > 0.0f) { if (mTransitioningToFullShadeProgress > 0.0f) {
@@ -2315,8 +2327,6 @@ public class NotificationPanelViewController extends PanelViewController {
qsBottomY = (int) MathUtils.lerp( qsBottomY = (int) MathUtils.lerp(
qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction); qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction);
} }
// to account for shade overshooting animation, see setSectionPadding method
if (mSectionPadding > 0) qsBottomY += mSectionPadding;
return qsBottomY; return qsBottomY;
} }
} }
@@ -2632,7 +2642,7 @@ public class NotificationPanelViewController extends PanelViewController {
int min = mStatusBarMinHeight; int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD) if (!(mBarState == KEYGUARD)
&& mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) { && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount()); int minHeight = mQsMinExpansionHeight;
min = Math.max(min, minHeight); min = Math.max(min, minHeight);
} }
int maxHeight; int maxHeight;
@@ -2644,8 +2654,8 @@ public class NotificationPanelViewController extends PanelViewController {
} }
maxHeight = Math.max(min, maxHeight); maxHeight = Math.max(min, maxHeight);
if (maxHeight == 0 || isNaN(maxHeight)) { if (maxHeight == 0 || isNaN(maxHeight)) {
Log.wtf(TAG, "maxPanelHeight is invalid. getOverExpansionAmount(): " Log.wtf(TAG, "maxPanelHeight is invalid. mOverExpansion: "
+ getOverExpansionAmount() + ", calculatePanelHeightQsExpanded: " + mOverExpansion + ", calculatePanelHeightQsExpanded: "
+ calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: " + calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: "
+ calculatePanelHeightShade() + ", mStatusBarMinHeight = " + calculatePanelHeightShade() + ", mStatusBarMinHeight = "
+ mStatusBarMinHeight + ", mQsMinExpansionHeight = " + mQsMinExpansionHeight); + mStatusBarMinHeight + ", mQsMinExpansionHeight = " + mQsMinExpansionHeight);
@@ -2807,23 +2817,6 @@ public class NotificationPanelViewController extends PanelViewController {
return alpha; return alpha;
} }
@Override
protected float getOverExpansionAmount() {
float result = mNotificationStackScrollLayoutController
.getCurrentOverScrollAmount(true /* top */);
if (isNaN(result)) {
Log.wtf(TAG, "OverExpansionAmount is NaN!");
}
return result;
}
@Override
protected float getOverExpansionPixels() {
return mNotificationStackScrollLayoutController
.getCurrentOverScrolledPixels(true /* top */);
}
/** /**
* Hides the header when notifications are colliding with it. * Hides the header when notifications are colliding with it.
*/ */
@@ -3018,31 +3011,15 @@ public class NotificationPanelViewController extends PanelViewController {
} }
@Override @Override
public void setSectionPadding(float padding) { public void setOverExpansion(float overExpansion) {
if (padding == mSectionPadding) { if (overExpansion == mOverExpansion) {
return; return;
} }
mSectionPadding = padding; super.setOverExpansion(overExpansion);
// TODO(b/172289889) update overscroll to spec // Translating the quick settings by half the overexpansion to center it in the background
} // frame
mQsFrame.setTranslationY(overExpansion / 2f);
@Override mNotificationStackScrollLayoutController.setOverExpansion(overExpansion);
protected void setOverExpansion(float overExpansion, boolean isPixels) {
if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
return;
}
if (mBarState != KEYGUARD) {
mNotificationStackScrollLayoutController.setOnHeightChangedListener(null);
if (isPixels) {
mNotificationStackScrollLayoutController.setOverScrolledPixels(
overExpansion, true /* onTop */, false /* animate */);
} else {
mNotificationStackScrollLayoutController.setOverScrollAmount(
overExpansion, true /* onTop */, false /* animate */);
}
mNotificationStackScrollLayoutController
.setOnHeightChangedListener(mOnHeightChangedListener);
}
} }
@Override @Override
@@ -3066,7 +3043,7 @@ public class NotificationPanelViewController extends PanelViewController {
mFalsingCollector.onTrackingStopped(); mFalsingCollector.onTrackingStopped();
super.onTrackingStopped(expand); super.onTrackingStopped(expand);
if (expand) { if (expand) {
mNotificationStackScrollLayoutController.setOverScrolledPixels(0.0f, true /* onTop */, mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
true /* animate */); true /* animate */);
} }
mNotificationStackScrollLayoutController.onPanelTrackingStopped(); mNotificationStackScrollLayoutController.onPanelTrackingStopped();
@@ -3111,18 +3088,6 @@ public class NotificationPanelViewController extends PanelViewController {
|| !isTracking()); || !isTracking());
} }
@Override
protected boolean fullyExpandedClearAllVisible() {
return mNotificationStackScrollLayoutController.isFooterViewNotGone()
&& mNotificationStackScrollLayoutController.isScrolledToBottom()
&& !mQsExpandImmediate;
}
@Override
protected boolean isClearAllVisible() {
return mNotificationStackScrollLayoutController.isFooterViewContentVisible();
}
@Override @Override
protected boolean isTrackingBlocked() { protected boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
@@ -3973,10 +3938,15 @@ public class NotificationPanelViewController extends PanelViewController {
} }
mLastOverscroll = 0f; mLastOverscroll = 0f;
mQsExpansionFromOverscroll = false; mQsExpansionFromOverscroll = false;
if (open) {
// During overscrolling, qsExpansion doesn't actually change that the qs is
// becoming expanded. Any layout could therefore reset the position again. Let's
// make sure we can expand
setOverScrolling(false);
}
setQsExpansion(mQsExpansionHeight); setQsExpansion(mQsExpansionHeight);
flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> { open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> {
mStackScrollerOverscrolling = false;
setOverScrolling(false); setOverScrolling(false);
updateQsState(); updateQsState();
}, false /* isClick */); }, false /* isClick */);
@@ -4392,7 +4362,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (mQsMaxExpansionHeight != oldMaxHeight) { if (mQsMaxExpansionHeight != oldMaxHeight) {
startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
} }
} else if (!mQsExpanded) { } else if (!mQsExpanded && mQsExpansionAnimator == null) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
} }
updateExpandedHeight(getExpandedHeight()); updateExpandedHeight(getExpandedHeight());

View File

@@ -53,7 +53,7 @@ public abstract class PanelBar extends FrameLayout {
if (DEBUG) LOG("go state: %d -> %d", mState, state); if (DEBUG) LOG("go state: %d -> %d", mState, state);
mState = state; mState = state;
if (mPanel != null) { if (mPanel != null) {
mPanel.getAmbientState().setIsShadeOpening(state == STATE_OPENING); mPanel.setIsShadeOpening(state == STATE_OPENING);
} }
} }

View File

@@ -68,12 +68,14 @@ public abstract class PanelViewController {
public static final String TAG = PanelView.class.getSimpleName(); public static final String TAG = PanelView.class.getSimpleName();
private static final int NO_FIXED_DURATION = -1; private static final int NO_FIXED_DURATION = -1;
private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
private static final long SHADE_OPEN_SPRING_BACK_DURATION = 200L; private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
private static final float MIN_OVERSCROLL = -50;
private static final float MAX_OVERSCROLL = 30; /**
* The factor of the usual high velocity that is needed in order to reach the maximum overshoot
* when flinging. A low value will make it that most flings will reach the maximum overshoot.
*/
private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
private float mFlingTarget;
private float mFlingVelocity;
protected long mDownTime; protected long mDownTime;
protected boolean mTouchSlopExceededBeforeDown; protected boolean mTouchSlopExceededBeforeDown;
private float mMinExpandHeight; private float mMinExpandHeight;
@@ -83,6 +85,22 @@ public abstract class PanelViewController {
protected boolean mIsLaunchAnimationRunning; protected boolean mIsLaunchAnimationRunning;
private int mFixedDuration = NO_FIXED_DURATION; private int mFixedDuration = NO_FIXED_DURATION;
protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>(); protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
protected float mOverExpansion;
/**
* The overshoot amount when the panel flings open
*/
private float mPanelFlingOvershootAmount;
/**
* The amount of pixels that we have overexpanded the last time with a gesture
*/
private float mLastGesturedOverExpansion = -1;
/**
* Is the current animator the spring back animation?
*/
private boolean mIsSpringBackAnimation;
private void logf(String fmt, Object... args) { private void logf(String fmt, Object... args) {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -254,6 +272,7 @@ public abstract class PanelViewController {
mTouchSlop = configuration.getScaledTouchSlop(); mTouchSlop = configuration.getScaledTouchSlop();
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
mUnlockFalsingThreshold = mResources.getDimensionPixelSize( mUnlockFalsingThreshold = mResources.getDimensionPixelSize(
R.dimen.unlock_falsing_threshold); R.dimen.unlock_falsing_threshold);
} }
@@ -529,21 +548,38 @@ public abstract class PanelViewController {
protected void flingToHeight(float vel, boolean expand, float target, protected void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { if (target == mExpandedHeight && mOverExpansion == 0.0f) {
// We're at the target and didn't fling and there's no overshoot
endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
mKeyguardStateController.notifyPanelFlingEnd(); mKeyguardStateController.notifyPanelFlingEnd();
notifyExpandingFinished(); notifyExpandingFinished();
return; return;
} }
mIsFlinging = true; mIsFlinging = true;
mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; // we want to perform an overshoot animation when flinging open
ValueAnimator animator = createHeightAnimator(target); final boolean addOverscroll = expand
mFlingTarget = target; && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
float overshootAmount = 0.0f;
if (addOverscroll) {
// Let's overshoot depending on the amount of velocity
overshootAmount = MathUtils.lerp(
0.2f,
1.0f,
MathUtils.saturate(vel
/ (mFlingAnimationUtils.getHighVelocityPxPerSecond()
* FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
}
ValueAnimator animator = createHeightAnimator(target, overshootAmount);
if (expand) { if (expand) {
if (expandBecauseOfFalsing && vel < 0) { if (expandBecauseOfFalsing && vel < 0) {
vel = 0; vel = 0;
} }
mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight()); mFlingAnimationUtils.apply(animator, mExpandedHeight,
target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight());
if (vel == 0) { if (vel == 0) {
animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
} }
@@ -570,7 +606,6 @@ public abstract class PanelViewController {
animator.setDuration(mFixedDuration); animator.setDuration(mFixedDuration);
} }
} }
mFlingVelocity = vel;
animator.addListener(new AnimatorListenerAdapter() { animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled; private boolean mCancelled;
@@ -586,7 +621,7 @@ public abstract class PanelViewController {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (expand && mFlingVelocity > 0) { if (shouldSpringBack && !mCancelled) {
// After the shade is flinged open to an overscrolled state, spring back // After the shade is flinged open to an overscrolled state, spring back
// the shade by reducing section padding to 0. // the shade by reducing section padding to 0.
springBack(); springBack();
@@ -600,14 +635,19 @@ public abstract class PanelViewController {
} }
private void springBack() { private void springBack() {
ValueAnimator animator = ValueAnimator.ofFloat(MAX_OVERSCROLL, 0); if (mOverExpansion == 0) {
onFlingEnd(false /* cancelled */);
return;
}
mIsSpringBackAnimation = true;
ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
animator.addUpdateListener( animator.addUpdateListener(
animation -> { animation -> {
setSectionPadding((float) animation.getAnimatedValue()); setOverExpansionInternal((float) animation.getAnimatedValue(),
setExpandedHeightInternal(mFlingTarget); false /* isFromGesture */);
}); });
animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
animator.setInterpolator(Interpolators.LINEAR); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() { animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled; private boolean mCancelled;
@Override @Override
@@ -616,6 +656,7 @@ public abstract class PanelViewController {
} }
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
mIsSpringBackAnimation = false;
onFlingEnd(mCancelled); onFlingEnd(mCancelled);
} }
}); });
@@ -625,6 +666,8 @@ public abstract class PanelViewController {
private void onFlingEnd(boolean cancelled) { private void onFlingEnd(boolean cancelled) {
mIsFlinging = false; mIsFlinging = false;
// No overshoot when the animation ends
setOverExpansionInternal(0, false /* isFromGesture */);
setAnimator(null); setAnimator(null);
mKeyguardStateController.notifyPanelFlingEnd(); mKeyguardStateController.notifyPanelFlingEnd();
if (!cancelled) { if (!cancelled) {
@@ -644,7 +687,7 @@ public abstract class PanelViewController {
public void setExpandedHeight(float height) { public void setExpandedHeight(float height) {
if (DEBUG) logf("setExpandedHeight(%.1f)", height); if (DEBUG) logf("setExpandedHeight(%.1f)", height);
setExpandedHeightInternal(height + getOverExpansionPixels()); setExpandedHeightInternal(height);
} }
protected void requestPanelHeightUpdate() { protected void requestPanelHeightUpdate() {
@@ -662,7 +705,7 @@ public abstract class PanelViewController {
return; return;
} }
if (mHeightAnimator != null) { if (mHeightAnimator != null && !mIsSpringBackAnimation) {
mPanelUpdateWhenAnimatorEnds = true; mPanelUpdateWhenAnimatorEnds = true;
return; return;
} }
@@ -677,38 +720,24 @@ public abstract class PanelViewController {
return stackHeightFraction; return stackHeightFraction;
} }
// When the shade is flinged open, add space before sections for overscroll effect.
private void maybeOverScrollForShadeFlingOpen(float height) {
if (!mBar.isShadeOpening() || mFlingVelocity <= 0) {
return;
}
final float padding = MathUtils.lerp(
MIN_OVERSCROLL, MAX_OVERSCROLL, getStackHeightFraction(height));
setSectionPadding(padding);
}
public void setExpandedHeightInternal(float h) { public void setExpandedHeightInternal(float h) {
if (isNaN(h)) { if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN"); Log.wtf(TAG, "ExpandedHeight set to NaN");
} }
maybeOverScrollForShadeFlingOpen(h);
if (mExpandLatencyTracking && h != 0f) { if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal( DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
mExpandLatencyTracking = false; mExpandLatencyTracking = false;
} }
float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); float maxPanelHeight = getMaxPanelHeight();
if (mHeightAnimator == null) { if (mHeightAnimator == null) {
float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); if (mTracking) {
if (getOverExpansionPixels() != overExpansionPixels && mTracking) { float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansion(overExpansionPixels, true /* isPixels */); setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
} }
mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); mExpandedHeight = Math.min(h, maxPanelHeight);
} else { } else {
mExpandedHeight = h; mExpandedHeight = h;
if (mOverExpandedBeforeFling) {
setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
}
} }
// If we are closing the panel and we are almost there due to a slow decelerating // If we are closing the panel and we are almost there due to a slow decelerating
@@ -720,7 +749,7 @@ public abstract class PanelViewController {
} }
} }
mExpandedFraction = Math.min(1f, mExpandedFraction = Math.min(1f,
fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion); maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
onHeightUpdated(mExpandedHeight); onHeightUpdated(mExpandedHeight);
notifyBarPanelExpansionChanged(); notifyBarPanelExpansionChanged();
} }
@@ -731,16 +760,31 @@ public abstract class PanelViewController {
*/ */
protected abstract boolean isTrackingBlocked(); protected abstract boolean isTrackingBlocked();
protected abstract void setSectionPadding(float padding); protected void setOverExpansion(float overExpansion) {
mOverExpansion = overExpansion;
}
protected abstract void setOverExpansion(float overExpansion, boolean isPixels); /**
* Set the current overexpansion
*
* @param overExpansion the amount of overexpansion to apply
* @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
*/
private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
if (!isFromGesture) {
mLastGesturedOverExpansion = -1;
setOverExpansion(overExpansion);
} else if (mLastGesturedOverExpansion != overExpansion) {
mLastGesturedOverExpansion = overExpansion;
final float heightForFullOvershoot = mView.getHeight() / 3.0f;
float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
}
}
protected abstract void onHeightUpdated(float expandedHeight); protected abstract void onHeightUpdated(float expandedHeight);
protected abstract float getOverExpansionAmount();
protected abstract float getOverExpansionPixels();
/** /**
* This returns the maximum height of the panel. Children should override this if their * This returns the maximum height of the panel. Children should override this if their
* desired height is not the full height. * desired height is not the full height.
@@ -977,9 +1021,32 @@ public abstract class PanelViewController {
} }
private ValueAnimator createHeightAnimator(float targetHeight) { private ValueAnimator createHeightAnimator(float targetHeight) {
return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
}
/**
* Create an animator that can also overshoot
*
* @param targetHeight the target height
* @param overshootAmount the amount of overshoot desired
*/
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
float startExpansion = mOverExpansion;
ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
animator.addUpdateListener( animator.addUpdateListener(
animation -> setExpandedHeightInternal((float) animation.getAnimatedValue())); animation -> {
if (overshootAmount > 0.0f
// Also remove the overExpansion when collapsing
|| (targetHeight == 0.0f && startExpansion != 0)) {
final float expansion = MathUtils.lerp(
startExpansion,
mPanelFlingOvershootAmount * overshootAmount,
Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
setExpandedHeightInternal((float) animation.getAnimatedValue());
});
return animator; return animator;
} }
@@ -989,7 +1056,7 @@ public abstract class PanelViewController {
mExpandedFraction, mExpandedFraction,
mExpandedFraction > 0f || mInstantExpanding mExpandedFraction > 0f || mInstantExpanding
|| isPanelVisibleBecauseOfHeadsUp() || mTracking || isPanelVisibleBecauseOfHeadsUp() || mTracking
|| mHeightAnimator != null); || mHeightAnimator != null && !mIsSpringBackAnimation);
} }
for (int i = 0; i < mExpansionListeners.size(); i++) { for (int i = 0; i < mExpansionListeners.size(); i++) {
mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking); mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
@@ -1037,14 +1104,6 @@ public abstract class PanelViewController {
public abstract void resetViews(boolean animate); public abstract void resetViews(boolean animate);
/**
* @return whether "Clear all" button will be visible when the panel is fully expanded
*/
protected abstract boolean fullyExpandedClearAllVisible();
protected abstract boolean isClearAllVisible();
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager; mHeadsUpManager = headsUpManager;
} }
@@ -1080,6 +1139,11 @@ public abstract class PanelViewController {
return new OnConfigurationChangedListener(); return new OnConfigurationChangedListener();
} }
/**
* Set that the panel is currently opening and not fully opened or closed.
*/
public abstract void setIsShadeOpening(boolean opening);
public class TouchHandler implements View.OnTouchListener { public class TouchHandler implements View.OnTouchListener {
public boolean onInterceptTouchEvent(MotionEvent event) { public boolean onInterceptTouchEvent(MotionEvent event) {
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -1108,7 +1172,7 @@ public abstract class PanelViewController {
switch (event.getActionMasked()) { switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
mStatusBar.userActivity(); mStatusBar.userActivity();
mAnimatingOnDown = mHeightAnimator != null; mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
mMinExpandHeight = 0.0f; mMinExpandHeight = 0.0f;
mDownTime = SystemClock.uptimeMillis(); mDownTime = SystemClock.uptimeMillis();
if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
@@ -1227,10 +1291,10 @@ public abstract class PanelViewController {
mCollapsedAndHeadsUpOnDown = mCollapsedAndHeadsUpOnDown =
isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
addMovement(event); addMovement(event);
if (!mGestureWaitForTouchSlop || (mHeightAnimator != null boolean regularHeightAnimationRunning = mHeightAnimator != null
&& !mHintAnimationRunning)) { && !mHintAnimationRunning && !mIsSpringBackAnimation;
mTouchSlopExceeded = if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
(mHeightAnimator != null && !mHintAnimationRunning) mTouchSlopExceeded = regularHeightAnimationRunning
|| mTouchSlopExceededBeforeDown; || mTouchSlopExceededBeforeDown;
cancelHeightAnimator(); cancelHeightAnimator();
onTrackingStarted(); onTrackingStarted();

View File

@@ -638,8 +638,6 @@ public class NotificationPanelViewTest extends SysuiTestCase {
public void testCancelSwipeWhileLocked_notifiesKeyguardState() { public void testCancelSwipeWhileLocked_notifiesKeyguardState() {
mStatusBarStateController.setState(KEYGUARD); mStatusBarStateController.setState(KEYGUARD);
mNotificationPanelViewController.setOverExpansion(100f, true);
// Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that
// the fling occurred and did not dismiss the keyguard. // the fling occurred and did not dismiss the keyguard.
mNotificationPanelViewController.flingToHeight( mNotificationPanelViewController.flingToHeight(