Merge "Expand notifications like accordian" into sc-dev

This commit is contained in:
Lyn Han
2021-04-15 17:42:51 +00:00
committed by Android (Google) Code Review
6 changed files with 446 additions and 146 deletions

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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;
}
/**

View File

@@ -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);
}
/**

View File

@@ -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);

View File

@@ -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<>();
}
/**