Merge "Expand notifications like accordian" into sc-dev
This commit is contained in:
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState;
|
||||
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
|
||||
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
|
||||
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
|
||||
import com.android.systemui.statusbar.notification.stack.ViewState;
|
||||
import com.android.systemui.statusbar.phone.NotificationIconContainer;
|
||||
|
||||
@@ -82,6 +83,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
private Rect mClipRect = new Rect();
|
||||
private int mCutoutHeight;
|
||||
private int mGapHeight;
|
||||
private int mIndexOfFirstViewInShelf = -1;
|
||||
private int mIndexOfFirstViewInOverflowingSection = -1;
|
||||
|
||||
private NotificationShelfController mController;
|
||||
|
||||
public NotificationShelf(Context context, AttributeSet attrs) {
|
||||
@@ -159,30 +163,49 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
}
|
||||
|
||||
/** Update the state of the shelf. */
|
||||
public void updateState(AmbientState ambientState) {
|
||||
public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
|
||||
AmbientState ambientState) {
|
||||
ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
|
||||
ShelfState viewState = (ShelfState) getViewState();
|
||||
if (mShowNotificationShelf && lastView != null) {
|
||||
float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
|
||||
+ ambientState.getStackTranslation();
|
||||
ExpandableViewState lastViewState = lastView.getViewState();
|
||||
float viewEnd = lastViewState.yTranslation + lastViewState.height;
|
||||
viewState.copyFrom(lastViewState);
|
||||
|
||||
viewState.height = getIntrinsicHeight();
|
||||
viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
|
||||
getFullyClosedTranslation());
|
||||
viewState.zTranslation = ambientState.getBaseZHeight();
|
||||
viewState.clipTopAmount = 0;
|
||||
viewState.alpha = 1f - ambientState.getHideAmount();
|
||||
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
|
||||
viewState.hideSensitive = false;
|
||||
viewState.xTranslation = getTranslationX();
|
||||
viewState.hasItemsInStableShelf = lastViewState.inShelf;
|
||||
viewState.firstViewInShelf = algorithmState.firstViewInShelf;
|
||||
viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
|
||||
if (mNotGoneIndex != -1) {
|
||||
viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
|
||||
}
|
||||
viewState.hasItemsInStableShelf = lastViewState.inShelf;
|
||||
|
||||
viewState.hidden = !mAmbientState.isShadeExpanded()
|
||||
|| mAmbientState.isQsCustomizerShowing();
|
||||
|| mAmbientState.isQsCustomizerShowing()
|
||||
|| algorithmState.firstViewInShelf == null;
|
||||
|
||||
final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
|
||||
algorithmState.firstViewInShelf);
|
||||
|
||||
if (mAmbientState.isExpansionChanging()
|
||||
&& algorithmState.firstViewInShelf != null
|
||||
&& indexOfFirstViewInShelf > 0) {
|
||||
|
||||
// Show shelf if section before it is showing.
|
||||
final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get(
|
||||
indexOfFirstViewInShelf - 1);
|
||||
if (viewBeforeShelf.getViewState().hidden) {
|
||||
viewState.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
|
||||
viewState.yTranslation = stackEnd - viewState.height;
|
||||
} else {
|
||||
viewState.hidden = true;
|
||||
viewState.location = ExpandableViewState.LOCATION_GONE;
|
||||
@@ -199,13 +222,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
if (!mShowNotificationShelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
mShelfIcons.resetViewStates();
|
||||
float shelfStart = getTranslationY();
|
||||
float numViewsInShelf = 0.0f;
|
||||
View lastChild = mAmbientState.getLastVisibleBackgroundChild();
|
||||
mNotGoneIndex = -1;
|
||||
float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
|
||||
// find the first view that doesn't overlap with the shelf
|
||||
int notGoneIndex = 0;
|
||||
int colorOfViewBeforeLast = NO_COLOR;
|
||||
@@ -219,7 +240,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
|
||||
boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
|
||||
|| (mAmbientState.isExpansionChanging()
|
||||
&& Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
|
||||
&& Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
|
||||
boolean expandingAnimated = mAmbientState.isExpansionChanging()
|
||||
&& !mAmbientState.isPanelTracking();
|
||||
int baseZHeight = mAmbientState.getBaseZHeight();
|
||||
@@ -233,22 +254,37 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float notificationClipEnd;
|
||||
boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
|
||||
|| child.isPinned();
|
||||
boolean isLastChild = child == lastChild;
|
||||
float rowTranslationY = child.getTranslationY();
|
||||
|
||||
final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
|
||||
expandingAnimated, isLastChild);
|
||||
|
||||
final float stackEnd = mAmbientState.getStackY()
|
||||
+ mAmbientState.getStackHeight();
|
||||
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
|
||||
if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
|
||||
notificationClipEnd = shelfStart + getIntrinsicHeight();
|
||||
notificationClipEnd = stackEnd;
|
||||
} else if (mAmbientState.isExpansionChanging()) {
|
||||
if (mIndexOfFirstViewInOverflowingSection != -1
|
||||
&& i >= mIndexOfFirstViewInOverflowingSection) {
|
||||
// Clip notifications in (section overflowing into shelf) to shelf start.
|
||||
notificationClipEnd = shelfStart - mPaddingBetweenElements;
|
||||
} else {
|
||||
// Clip notifications before the section overflowing into shelf
|
||||
// to stackEnd because we do not show the shelf if the section right before the
|
||||
// shelf is still hidden.
|
||||
notificationClipEnd = stackEnd;
|
||||
}
|
||||
} else {
|
||||
notificationClipEnd = shelfStart - mPaddingBetweenElements;
|
||||
}
|
||||
int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex);
|
||||
clipTopAmount = Math.max(clipTop, clipTopAmount);
|
||||
|
||||
final float inShelfAmount = updateShelfTransformation(child, scrollingFast,
|
||||
expandingAnimated, isLastChild);
|
||||
// If the current row is an ExpandableNotificationRow, update its color, roundedness,
|
||||
// and icon state.
|
||||
if (child instanceof ExpandableNotificationRow) {
|
||||
@@ -314,19 +350,23 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
distanceToGapTop / mGapHeight);
|
||||
previousAnv.setBottomRoundness(firstElementRoundness,
|
||||
false /* don't animate */);
|
||||
backgroundTop = (int) distanceToGapBottom;
|
||||
}
|
||||
}
|
||||
previousAnv = anv;
|
||||
}
|
||||
}
|
||||
|
||||
clipTransientViews();
|
||||
|
||||
setClipTopAmount(clipTopAmount);
|
||||
boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight();
|
||||
if (mShowNotificationShelf) {
|
||||
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
boolean isHidden = getViewState().hidden
|
||||
|| clipTopAmount >= getIntrinsicHeight()
|
||||
|| !mShowNotificationShelf
|
||||
|| numViewsInShelf < 1f;
|
||||
|
||||
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
|
||||
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
|
||||
setBackgroundTop(backgroundTop);
|
||||
setFirstElementRoundness(firstElementRoundness);
|
||||
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
|
||||
@@ -339,11 +379,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
continue;
|
||||
}
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
|
||||
updateIconClipAmount(row);
|
||||
updateContinuousClipping(row);
|
||||
}
|
||||
boolean hideBackground = numViewsInShelf < 1.0f;
|
||||
setHideBackground(hideBackground || backgroundForceHidden);
|
||||
boolean hideBackground = isHidden;
|
||||
setHideBackground(hideBackground);
|
||||
if (mNotGoneIndex == -1) {
|
||||
mNotGoneIndex = notGoneIndex;
|
||||
}
|
||||
@@ -476,10 +515,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
/**
|
||||
* @return the amount how much this notification is in the shelf
|
||||
*/
|
||||
private float updateShelfTransformation(ExpandableView view, boolean scrollingFast,
|
||||
private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
|
||||
boolean expandingAnimated, boolean isLastChild) {
|
||||
|
||||
// Let calculate how much the view is in the shelf
|
||||
// Let's calculate how much the view is in the shelf
|
||||
float viewStart = view.getTranslationY();
|
||||
int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
|
||||
float iconTransformStart = calculateIconTransformationStart(view);
|
||||
@@ -496,15 +535,21 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
transformDistance,
|
||||
view.getMinHeight() - getIntrinsicHeight());
|
||||
}
|
||||
|
||||
float viewEnd = viewStart + fullHeight;
|
||||
float fullTransitionAmount = 0.0f;
|
||||
float iconTransitionAmount = 0.0f;
|
||||
float shelfStart = getTranslationY();
|
||||
|
||||
if (viewEnd >= shelfStart
|
||||
if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
|
||||
// TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
|
||||
if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
|
||||
fullTransitionAmount = 1f;
|
||||
iconTransitionAmount = 1f;
|
||||
}
|
||||
} else if (viewEnd >= shelfStart
|
||||
&& (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
|
||||
&& (mAmbientState.isShadeExpanded()
|
||||
|| (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
|
||||
|| (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
|
||||
|
||||
if (viewStart < shelfStart) {
|
||||
float fullAmount = (shelfStart - viewStart) / fullHeight;
|
||||
@@ -572,7 +617,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
&& !mNoAnimationsInThisFrame;
|
||||
}
|
||||
iconState.clampedAppearAmount = clampedAmount;
|
||||
setIconTransformationAmount(view, transitionAmount, isLastChild);
|
||||
setIconTransformationAmount(view, transitionAmount);
|
||||
}
|
||||
|
||||
private boolean isTargetClipped(ExpandableView view) {
|
||||
@@ -585,12 +630,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
+ view.getContentTranslation()
|
||||
+ view.getRelativeTopPadding(target)
|
||||
+ target.getHeight();
|
||||
|
||||
return endOfTarget >= getTranslationY() - mPaddingBetweenElements;
|
||||
}
|
||||
|
||||
private void setIconTransformationAmount(ExpandableView view, float transitionAmount,
|
||||
boolean isLastChild) {
|
||||
private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
|
||||
if (!(view instanceof ExpandableNotificationRow)) {
|
||||
return;
|
||||
}
|
||||
@@ -601,7 +644,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
return;
|
||||
}
|
||||
iconState.alpha = transitionAmount;
|
||||
|
||||
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
|
||||
iconState.hidden = isAppearing
|
||||
|| (view instanceof ExpandableNotificationRow
|
||||
@@ -610,8 +652,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
|| (transitionAmount == 0.0f && !iconState.isAnimating(icon))
|
||||
|| row.isAboveShelf()
|
||||
|| row.showingPulsing()
|
||||
|| (!row.isInShelf() && isLastChild)
|
||||
|| row.getTranslationZ() > mAmbientState.getBaseZHeight();
|
||||
|
||||
iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
|
||||
|
||||
// Fade in icons at shelf start
|
||||
@@ -790,8 +832,19 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
mController = notificationShelfController;
|
||||
}
|
||||
|
||||
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
|
||||
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
|
||||
}
|
||||
|
||||
public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
|
||||
mIndexOfFirstViewInOverflowingSection =
|
||||
mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
|
||||
}
|
||||
|
||||
private class ShelfState extends ExpandableViewState {
|
||||
private boolean hasItemsInStableShelf;
|
||||
private ExpandableView firstViewInShelf;
|
||||
private ExpandableView firstViewInOverflowSection;
|
||||
|
||||
@Override
|
||||
public void applyToView(View view) {
|
||||
@@ -800,6 +853,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
}
|
||||
|
||||
super.applyToView(view);
|
||||
setIndexOfFirstViewInShelf(firstViewInShelf);
|
||||
setFirstViewInOverflowingSection(firstViewInOverflowSection);
|
||||
updateAppearance();
|
||||
setHasItemsInStableShelf(hasItemsInStableShelf);
|
||||
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
|
||||
@@ -812,6 +867,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
}
|
||||
|
||||
super.animateTo(child, properties);
|
||||
setIndexOfFirstViewInShelf(firstViewInShelf);
|
||||
setFirstViewInOverflowingSection(firstViewInOverflowSection);
|
||||
updateAppearance();
|
||||
setHasItemsInStableShelf(hasItemsInStableShelf);
|
||||
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
|
||||
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
|
||||
import com.android.systemui.statusbar.notification.stack.AmbientState;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
|
||||
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController;
|
||||
import com.android.systemui.statusbar.phone.NotificationIconContainer;
|
||||
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
|
||||
@@ -103,9 +104,10 @@ public class NotificationShelfController {
|
||||
return mView.getHeight();
|
||||
}
|
||||
|
||||
public void updateState(AmbientState ambientState) {
|
||||
public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
|
||||
AmbientState ambientState) {
|
||||
mAmbientState = ambientState;
|
||||
mView.updateState(ambientState);
|
||||
mView.updateState(algorithmState, ambientState);
|
||||
}
|
||||
|
||||
public int getIntrinsicHeight() {
|
||||
|
||||
@@ -60,7 +60,7 @@ public class AmbientState {
|
||||
private NotificationShelf mShelf;
|
||||
private int mZDistanceBetweenElements;
|
||||
private int mBaseZHeight;
|
||||
private int mMaxLayoutHeight;
|
||||
private int mContentHeight;
|
||||
private ExpandableView mLastVisibleBackgroundChild;
|
||||
private float mCurrentScrollVelocity;
|
||||
private int mStatusBarState;
|
||||
@@ -84,6 +84,75 @@ public class AmbientState {
|
||||
private boolean mIsShadeOpening;
|
||||
private float mSectionPadding;
|
||||
|
||||
/** Distance of top of notifications panel from top of screen. */
|
||||
private float mStackY = 0;
|
||||
|
||||
/** Height of notifications panel. */
|
||||
private float mStackHeight = 0;
|
||||
|
||||
/** Fraction of shade expansion. */
|
||||
private float mExpansionFraction;
|
||||
|
||||
/** Height of the notifications panel without top padding when expansion completes. */
|
||||
private float mStackEndHeight;
|
||||
|
||||
/**
|
||||
* @return Height of the notifications panel without top padding when expansion completes.
|
||||
*/
|
||||
public float getStackEndHeight() {
|
||||
return mStackEndHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stackEndHeight Height of the notifications panel without top padding
|
||||
* when expansion completes.
|
||||
*/
|
||||
public void setStackEndHeight(float stackEndHeight) {
|
||||
mStackEndHeight = stackEndHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stackY Distance of top of notifications panel from top of screen.
|
||||
*/
|
||||
public void setStackY(float stackY) {
|
||||
mStackY = stackY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Distance of top of notifications panel from top of screen.
|
||||
*/
|
||||
public float getStackY() {
|
||||
return mStackY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expansionFraction Fraction of shade expansion.
|
||||
*/
|
||||
public void setExpansionFraction(float expansionFraction) {
|
||||
mExpansionFraction = expansionFraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Fraction of shade expansion.
|
||||
*/
|
||||
public float getExpansionFraction() {
|
||||
return mExpansionFraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stackHeight Height of notifications panel.
|
||||
*/
|
||||
public void setStackHeight(float stackHeight) {
|
||||
mStackHeight = stackHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Height of notifications panel.
|
||||
*/
|
||||
public float getStackHeight() {
|
||||
return mStackHeight;
|
||||
}
|
||||
|
||||
/** Tracks the state from AlertingNotificationManager#hasNotifications() */
|
||||
private boolean mHasAlertEntries;
|
||||
|
||||
@@ -263,8 +332,8 @@ public class AmbientState {
|
||||
if (mDozeAmount == 1.0f && !isPulseExpanding()) {
|
||||
return mShelf.getHeight();
|
||||
}
|
||||
int height = Math.max(mLayoutMinHeight,
|
||||
Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding);
|
||||
int height = (int) Math.max(mLayoutMinHeight,
|
||||
Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
|
||||
if (ignorePulseHeight) {
|
||||
return height;
|
||||
}
|
||||
@@ -313,8 +382,12 @@ public class AmbientState {
|
||||
return mShelf;
|
||||
}
|
||||
|
||||
public void setLayoutMaxHeight(int maxLayoutHeight) {
|
||||
mMaxLayoutHeight = maxLayoutHeight;
|
||||
public void setContentHeight(int contentHeight) {
|
||||
mContentHeight = contentHeight;
|
||||
}
|
||||
|
||||
public float getContentHeight() {
|
||||
return mContentHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -658,6 +658,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
y = getHeight() - getEmptyBottomMargin();
|
||||
mDebugPaint.setColor(Color.GREEN);
|
||||
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
|
||||
|
||||
y = (int) (mAmbientState.getStackY());
|
||||
mDebugPaint.setColor(Color.CYAN);
|
||||
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
|
||||
|
||||
y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
|
||||
mDebugPaint.setColor(Color.BLUE);
|
||||
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,11 +1131,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
mTopPaddingNeedsAnimation = true;
|
||||
mNeedsAnimation = true;
|
||||
}
|
||||
updateStackPosition();
|
||||
requestChildrenUpdate();
|
||||
notifyHeightChangeListener(null, animate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply expansion fraction to the y position and height of the notifications panel.
|
||||
*/
|
||||
private void updateStackPosition() {
|
||||
// Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
|
||||
mAmbientState.setStackY(
|
||||
MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction()));
|
||||
final float shadeBottom = getHeight() - getEmptyBottomMargin();
|
||||
mAmbientState.setStackEndHeight(shadeBottom - mTopPadding);
|
||||
mAmbientState.setStackHeight(
|
||||
MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the height of the panel.
|
||||
*
|
||||
@@ -1135,6 +1157,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
*/
|
||||
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
|
||||
public void setExpandedHeight(float height) {
|
||||
final float shadeBottom = getHeight() - getEmptyBottomMargin();
|
||||
final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f);
|
||||
mAmbientState.setExpansionFraction(expansionFraction);
|
||||
updateStackPosition();
|
||||
|
||||
mExpandedHeight = height;
|
||||
setIsExpanded(height > 0);
|
||||
int minExpansionHeight = getMinExpansionHeight();
|
||||
@@ -2067,7 +2094,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
|
||||
updateScrollability();
|
||||
clampScrollPosition();
|
||||
mAmbientState.setLayoutMaxHeight(mContentHeight);
|
||||
updateStackPosition();
|
||||
mAmbientState.setContentHeight(mContentHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -788,6 +788,10 @@ public class NotificationStackScrollLayoutController {
|
||||
return mView.getTranslationX();
|
||||
}
|
||||
|
||||
public int indexOfChild(View view) {
|
||||
return mView.indexOfChild(view);
|
||||
}
|
||||
|
||||
public void setOnHeightChangedListener(
|
||||
ExpandableView.OnHeightChangedListener listener) {
|
||||
mView.setOnHeightChangedListener(listener);
|
||||
|
||||
@@ -20,21 +20,22 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.EmptyShadeView;
|
||||
import com.android.systemui.statusbar.NotificationShelf;
|
||||
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
|
||||
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableView;
|
||||
import com.android.systemui.statusbar.notification.row.FooterView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
|
||||
@@ -92,20 +93,16 @@ public class StackScrollAlgorithm {
|
||||
|
||||
// First we reset the view states to their default values.
|
||||
resetChildViewStates();
|
||||
|
||||
initAlgorithmState(mHostView, algorithmState, ambientState);
|
||||
|
||||
updatePositionsForState(algorithmState, ambientState);
|
||||
|
||||
updateZValuesForState(algorithmState, ambientState);
|
||||
|
||||
updateHeadsUpStates(algorithmState, ambientState);
|
||||
updatePulsingStates(algorithmState, ambientState);
|
||||
|
||||
updateDimmedActivatedHideSensitive(ambientState, algorithmState);
|
||||
updateClipping(algorithmState, ambientState);
|
||||
updateSpeedBumpState(algorithmState, speedBumpIndex);
|
||||
updateShelfState(ambientState);
|
||||
updateShelfState(algorithmState, ambientState);
|
||||
getNotificationChildrenStates(algorithmState, ambientState);
|
||||
}
|
||||
|
||||
@@ -144,10 +141,13 @@ public class StackScrollAlgorithm {
|
||||
|
||||
}
|
||||
|
||||
private void updateShelfState(AmbientState ambientState) {
|
||||
private void updateShelfState(
|
||||
StackScrollAlgorithmState algorithmState,
|
||||
AmbientState ambientState) {
|
||||
|
||||
NotificationShelf shelf = ambientState.getShelf();
|
||||
if (shelf != null) {
|
||||
shelf.updateState(ambientState);
|
||||
shelf.updateState(algorithmState, ambientState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,8 @@ public class StackScrollAlgorithm {
|
||||
&& ((ExpandableNotificationRow) child).isPinned();
|
||||
if (mClipNotificationScrollToTop
|
||||
&& (!state.inShelf || (isHeadsUp && !firstHeadsUp))
|
||||
&& newYTranslation < clipStart) {
|
||||
&& newYTranslation < clipStart
|
||||
&& !ambientState.isShadeOpening()) {
|
||||
// The previous view is overlapping on top, clip!
|
||||
float overlapAmount = clipStart - newYTranslation;
|
||||
state.clipTopAmount = (int) overlapAmount;
|
||||
@@ -217,7 +218,6 @@ public class StackScrollAlgorithm {
|
||||
private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
|
||||
AmbientState ambientState) {
|
||||
float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
|
||||
|
||||
int scrollY = ambientState.getScrollY();
|
||||
|
||||
// Due to the overScroller, the stackscroller can have negative scroll state. This is
|
||||
@@ -230,7 +230,6 @@ public class StackScrollAlgorithm {
|
||||
state.visibleChildren.clear();
|
||||
state.visibleChildren.ensureCapacity(childCount);
|
||||
int notGoneIndex = 0;
|
||||
ExpandableView lastView = null;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
|
||||
if (v.getVisibility() != View.GONE) {
|
||||
@@ -255,12 +254,101 @@ public class StackScrollAlgorithm {
|
||||
}
|
||||
}
|
||||
}
|
||||
ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
|
||||
state.indexOfExpandingNotification = expandingNotification != null
|
||||
? expandingNotification.isChildInGroup()
|
||||
? state.visibleChildren.indexOf(expandingNotification.getNotificationParent())
|
||||
: state.visibleChildren.indexOf(expandingNotification)
|
||||
: -1;
|
||||
|
||||
state.firstViewInShelf = null;
|
||||
// Save y, sectionStart, sectionEnd from when shade is fully expanded.
|
||||
// Consider updating these states in updateContentView instead so that we don't have to
|
||||
// recalculate in every frame.
|
||||
float currentY = -scrollY;
|
||||
int sectionStartIndex = 0;
|
||||
int sectionEndIndex = 0;
|
||||
for (int i = 0; i < state.visibleChildren.size(); i++) {
|
||||
final ExpandableView view = state.visibleChildren.get(i);
|
||||
// Add space between sections.
|
||||
final boolean applyGapHeight = childNeedsGapHeight(
|
||||
ambientState.getSectionProvider(), i,
|
||||
view, getPreviousView(i, state));
|
||||
if (applyGapHeight) {
|
||||
currentY += mGapHeight;
|
||||
}
|
||||
|
||||
// Save index of first view in the shelf
|
||||
final float shelfStart = ambientState.getStackEndHeight()
|
||||
- ambientState.getShelf().getIntrinsicHeight();
|
||||
if (currentY >= shelfStart
|
||||
&& !(view instanceof FooterView)
|
||||
&& state.firstViewInShelf == null) {
|
||||
state.firstViewInShelf = view;
|
||||
}
|
||||
|
||||
// Record y position when fully expanded
|
||||
ExpansionData expansionData = new ExpansionData();
|
||||
expansionData.fullyExpandedY = currentY;
|
||||
state.expansionData.put(view, expansionData);
|
||||
|
||||
if (ambientState.getSectionProvider()
|
||||
.beginsSection(view, getPreviousView(i, state))) {
|
||||
|
||||
// Save section start/end for views in the section before this new section
|
||||
ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex);
|
||||
final float sectionStart =
|
||||
state.expansionData.get(sectionStartView).fullyExpandedY;
|
||||
|
||||
ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex);
|
||||
float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY
|
||||
+ sectionEndView.getIntrinsicHeight();
|
||||
|
||||
// If we show the shelf, trim section end to shelf start
|
||||
// This means section end > start for views in the shelf
|
||||
if (state.firstViewInShelf != null && sectionEnd > shelfStart) {
|
||||
sectionEnd = shelfStart;
|
||||
}
|
||||
|
||||
// Update section bounds of every view in the previous section
|
||||
// Consider using shared SectionInfo for views in same section to avoid looping back
|
||||
for (int j = sectionStartIndex; j < i; j++) {
|
||||
ExpandableView sectionView = state.visibleChildren.get(j);
|
||||
ExpansionData viewExpansionData =
|
||||
state.expansionData.get(sectionView);
|
||||
viewExpansionData.sectionStart = sectionStart;
|
||||
viewExpansionData.sectionEnd = sectionEnd;
|
||||
state.expansionData.put(sectionView, viewExpansionData);
|
||||
}
|
||||
sectionStartIndex = i;
|
||||
|
||||
if (view instanceof FooterView) {
|
||||
// Also record section bounds for FooterView (same as its own)
|
||||
// because it is the last view and we won't get to this point again
|
||||
// after the loop ends
|
||||
ExpansionData footerExpansionData = state.expansionData.get(view);
|
||||
footerExpansionData.sectionStart = expansionData.fullyExpandedY;
|
||||
footerExpansionData.sectionEnd = expansionData.fullyExpandedY
|
||||
+ view.getIntrinsicHeight();
|
||||
state.expansionData.put(view, footerExpansionData);
|
||||
}
|
||||
}
|
||||
sectionEndIndex = i;
|
||||
currentY = currentY
|
||||
+ getMaxAllowedChildHeight(view)
|
||||
+ mPaddingBetweenElements;
|
||||
}
|
||||
|
||||
// Which view starts the section of the view right before the shelf?
|
||||
// Save it for later when we clip views in that section to shelf start.
|
||||
state.firstViewInOverflowSection = null;
|
||||
if (state.firstViewInShelf != null) {
|
||||
ExpandableView nextView = null;
|
||||
final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf);
|
||||
for (int i = startIndex - 1; i >= 0; i--) {
|
||||
ExpandableView view = state.visibleChildren.get(i);
|
||||
if (nextView != null && ambientState.getSectionProvider()
|
||||
.beginsSection(nextView, view)) {
|
||||
break;
|
||||
}
|
||||
nextView = view;
|
||||
}
|
||||
state.firstViewInOverflowSection = nextView;
|
||||
}
|
||||
}
|
||||
|
||||
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
|
||||
@@ -272,6 +360,10 @@ public class StackScrollAlgorithm {
|
||||
return notGoneIndex;
|
||||
}
|
||||
|
||||
private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) {
|
||||
return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the positions for the views. This is the main part of the algorithm.
|
||||
*
|
||||
@@ -288,6 +380,15 @@ public class StackScrollAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
|
||||
int i) {
|
||||
expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
|
||||
if (currentYPosition <= 0) {
|
||||
expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/172289889) polish shade open from HUN
|
||||
/**
|
||||
* Populates the {@link ExpandableViewState} for a single child.
|
||||
*
|
||||
@@ -306,53 +407,84 @@ public class StackScrollAlgorithm {
|
||||
StackScrollAlgorithmState algorithmState,
|
||||
AmbientState ambientState,
|
||||
float currentYPosition) {
|
||||
ExpandableView child = algorithmState.visibleChildren.get(i);
|
||||
ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
|
||||
|
||||
ExpandableView view = algorithmState.visibleChildren.get(i);
|
||||
ExpandableViewState viewState = view.getViewState();
|
||||
viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
|
||||
viewState.alpha = 1f - ambientState.getHideAmount();
|
||||
|
||||
if (view.mustStayOnScreen() && viewState.yTranslation >= 0) {
|
||||
// Even if we're not scrolled away we're in view and we're also not in the
|
||||
// shelf. We can relax the constraints and let us scroll off the top!
|
||||
float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
|
||||
viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
|
||||
}
|
||||
|
||||
// TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState
|
||||
// Get fraction of section showing, and later apply it to view height and gaps between views
|
||||
float sectionFraction = 1f;
|
||||
boolean showSection = true;
|
||||
|
||||
if (!ambientState.isOnKeyguard()
|
||||
&& !ambientState.isPulseExpanding()
|
||||
&& ambientState.isExpansionChanging()) {
|
||||
|
||||
final ExpansionData expansionData = algorithmState.expansionData.get(view);
|
||||
final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart;
|
||||
sectionFraction = MathUtils.constrain(
|
||||
(ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight,
|
||||
0f, 1f);
|
||||
showSection = expansionData.sectionStart < ambientState.getStackHeight();
|
||||
}
|
||||
|
||||
// Add gap between sections.
|
||||
final boolean applyGapHeight =
|
||||
childNeedsGapHeight(
|
||||
ambientState.getSectionProvider(), i,
|
||||
child, previousChild);
|
||||
ExpandableViewState childViewState = child.getViewState();
|
||||
childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
|
||||
|
||||
view, getPreviousView(i, algorithmState));
|
||||
if (applyGapHeight) {
|
||||
currentYPosition += mGapHeight;
|
||||
}
|
||||
int childHeight = getMaxAllowedChildHeight(child);
|
||||
childViewState.yTranslation = currentYPosition;
|
||||
childViewState.alpha = 1f - ambientState.getHideAmount();
|
||||
|
||||
boolean isFooterView = child instanceof FooterView;
|
||||
boolean isEmptyShadeView = child instanceof EmptyShadeView;
|
||||
|
||||
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
|
||||
float inset = ambientState.getTopPadding() + ambientState.getStackTranslation()
|
||||
+ ambientState.getSectionPadding();
|
||||
if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
|
||||
// Even if we're not scrolled away we're in view and we're also not in the
|
||||
// shelf. We can relax the constraints and let us scroll off the top!
|
||||
float end = childViewState.yTranslation + childViewState.height + inset;
|
||||
childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
|
||||
}
|
||||
if (isFooterView) {
|
||||
childViewState.yTranslation = Math.min(childViewState.yTranslation,
|
||||
ambientState.getInnerHeight() - childHeight);
|
||||
} else if (isEmptyShadeView) {
|
||||
childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
|
||||
+ ambientState.getStackTranslation() * 0.25f;
|
||||
} else if (child != ambientState.getTrackedHeadsUpRow()) {
|
||||
clampPositionToShelf(child, childViewState, ambientState);
|
||||
currentYPosition += sectionFraction * mGapHeight;
|
||||
}
|
||||
|
||||
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
|
||||
if (currentYPosition <= 0) {
|
||||
childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
|
||||
}
|
||||
if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
|
||||
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
|
||||
viewState.yTranslation = currentYPosition;
|
||||
|
||||
if (view instanceof SectionHeaderView) {
|
||||
// Add padding before sections for overscroll effect.
|
||||
viewState.yTranslation += ambientState.getSectionPadding();
|
||||
}
|
||||
|
||||
childViewState.yTranslation += inset;
|
||||
if (view != ambientState.getTrackedHeadsUpRow()) {
|
||||
if (ambientState.isExpansionChanging()) {
|
||||
viewState.hidden = !showSection;
|
||||
viewState.inShelf = algorithmState.firstViewInShelf != null
|
||||
&& i >= algorithmState.visibleChildren.indexOf(
|
||||
algorithmState.firstViewInShelf)
|
||||
&& !(view instanceof FooterView);
|
||||
} else {
|
||||
// When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
|
||||
// to shelf start, thereby hiding all notifications (except the first one, which we
|
||||
// later unhide in updatePulsingState)
|
||||
final int shelfStart = ambientState.getInnerHeight()
|
||||
- ambientState.getShelf().getIntrinsicHeight();
|
||||
if (!(view instanceof FooterView)) {
|
||||
viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
|
||||
}
|
||||
if (viewState.yTranslation >= shelfStart) {
|
||||
viewState.hidden = !view.isExpandAnimationRunning()
|
||||
&& !view.hasExpandingChild()
|
||||
&& !(view instanceof FooterView);
|
||||
viewState.inShelf = true;
|
||||
// Notifications in the shelf cannot be visible HUNs.
|
||||
viewState.headsUpIsVisible = false;
|
||||
}
|
||||
}
|
||||
viewState.height = (int) MathUtils.lerp(
|
||||
0, getMaxAllowedChildHeight(view), sectionFraction);
|
||||
}
|
||||
|
||||
currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
|
||||
setLocation(view.getViewState(), currentYPosition, i);
|
||||
viewState.yTranslation += ambientState.getStackY();
|
||||
return currentYPosition;
|
||||
}
|
||||
|
||||
@@ -393,10 +525,10 @@ public class StackScrollAlgorithm {
|
||||
int visibleIndex,
|
||||
View child,
|
||||
View previousChild) {
|
||||
|
||||
boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
|
||||
&& visibleIndex > 0;
|
||||
return needsGapHeight;
|
||||
return sectionProvider.beginsSection(child, previousChild)
|
||||
&& visibleIndex > 0
|
||||
&& !(previousChild instanceof SilentHeader)
|
||||
&& !(child instanceof FooterView);
|
||||
}
|
||||
|
||||
private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
|
||||
@@ -514,42 +646,6 @@ public class StackScrollAlgorithm {
|
||||
childState.yTranslation = newTranslation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp the height of the child down such that its end is at most on the beginning of
|
||||
* the shelf.
|
||||
*
|
||||
* @param childViewState the view state of the child
|
||||
* @param ambientState the ambient state
|
||||
*/
|
||||
private void clampPositionToShelf(ExpandableView child,
|
||||
ExpandableViewState childViewState,
|
||||
AmbientState ambientState) {
|
||||
if (ambientState.getShelf() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
|
||||
boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null
|
||||
&& mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow);
|
||||
|
||||
int shelfStart = ambientState.getInnerHeight()
|
||||
- ambientState.getShelf().getIntrinsicHeight();
|
||||
if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) {
|
||||
// Don't show none heads-up notifications while in appearing phase.
|
||||
childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
|
||||
}
|
||||
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
|
||||
if (child instanceof SectionHeaderView) {
|
||||
// Add padding before sections for overscroll effect.
|
||||
childViewState.yTranslation += ambientState.getSectionPadding();
|
||||
}
|
||||
if (childViewState.yTranslation >= shelfStart) {
|
||||
childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
|
||||
childViewState.inShelf = true;
|
||||
childViewState.headsUpIsVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected int getMaxAllowedChildHeight(View child) {
|
||||
if (child instanceof ExpandableView) {
|
||||
ExpandableView expandableView = (ExpandableView) child;
|
||||
@@ -641,6 +737,35 @@ public class StackScrollAlgorithm {
|
||||
this.mIsExpanded = isExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data used to layout views while shade expansion changes.
|
||||
*/
|
||||
public class ExpansionData {
|
||||
|
||||
/**
|
||||
* Y position of top of first view in section.
|
||||
*/
|
||||
public float sectionStart;
|
||||
|
||||
/**
|
||||
* Y position of bottom of last view in section.
|
||||
*/
|
||||
public float sectionEnd;
|
||||
|
||||
/**
|
||||
* Y position of view when shade is fully expanded.
|
||||
* Does not include distance between top notifications panel and top of screen.
|
||||
*/
|
||||
public float fullyExpandedY;
|
||||
|
||||
/**
|
||||
* Whether this notification is in the same section as the notification right before the
|
||||
* shelf. Used to determine which notification should be clipped to shelf start while
|
||||
* shade expansion changes.
|
||||
*/
|
||||
public boolean inOverflowingSection;
|
||||
}
|
||||
|
||||
public class StackScrollAlgorithmState {
|
||||
|
||||
/**
|
||||
@@ -648,16 +773,27 @@ public class StackScrollAlgorithm {
|
||||
*/
|
||||
public int scrollY;
|
||||
|
||||
/**
|
||||
* First view in shelf.
|
||||
*/
|
||||
public ExpandableView firstViewInShelf;
|
||||
|
||||
/**
|
||||
* First view in section overflowing into shelf while shade expansion changes.
|
||||
*/
|
||||
public ExpandableView firstViewInOverflowSection;
|
||||
|
||||
/**
|
||||
* Map of view to ExpansionData used for layout during shade expansion.
|
||||
* Use view instead of index as key, because visibleChildren indices do not match the ones
|
||||
* used in the shelf.
|
||||
*/
|
||||
public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The children from the host view which are not gone.
|
||||
*/
|
||||
public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
|
||||
|
||||
private int indexOfExpandingNotification;
|
||||
|
||||
public int getIndexOfExpandingNotification() {
|
||||
return indexOfExpandingNotification;
|
||||
}
|
||||
public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user