summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSelim Cinek <cinek@google.com>2014-04-11 21:23:30 +0200
committerSelim Cinek <cinek@google.com>2014-04-22 17:00:05 +0200
commit343e6e258ab6a9f647eabebaed05ce3acafd2ff1 (patch)
tree8c62bd61e7f78be608c4646f2fc1f88a598d139b
parent6513f35cbb2b72d89e5fa72c6d6e7d95b899bd23 (diff)
downloadframeworks_base-343e6e258ab6a9f647eabebaed05ce3acafd2ff1.zip
frameworks_base-343e6e258ab6a9f647eabebaed05ce3acafd2ff1.tar.gz
frameworks_base-343e6e258ab6a9f647eabebaed05ce3acafd2ff1.tar.bz2
Avoiding intermediate states in NotificationStackScroller
The StackScrollAlgorithm was modified such that the notifications now don't layout anymore during scrolling and therefore intermediate states are avoided except for the first card. Also made the top stack a bit smaller and fixed a bug where the scrolling was not working on the very first try. Bug: 14080821 Bug: 14081652 Change-Id: I924a9f8532486856fc2ecd88f6c10d26023a5bc3
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java334
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java110
6 files changed, 389 insertions, 178 deletions
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3047f15..0604817 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -250,7 +250,7 @@
<dimen name="notification_stack_margin_bottom">0dp</dimen>
<!-- Space reserved for the cards behind the top card in the top stack -->
- <dimen name="top_stack_peek_amount">24dp</dimen>
+ <dimen name="top_stack_peek_amount">12dp</dimen>
<!-- Space reserved for the cards behind the top card in the bottom stack -->
<dimen name="bottom_stack_peek_amount">18dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 1832d37..8dd3f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -195,6 +195,12 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
mGravity = Gravity.TOP;
mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
mScaleAnimation.setDuration(EXPAND_DURATION);
+ mScaleAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCallback.setUserLockedChild(mCurrView, false);
+ }
+ });
mPopLimit = mContext.getResources().getDimension(R.dimen.blinds_pop_threshold);
mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
@@ -549,8 +555,9 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
mScaleAnimation.setFloatValues(targetHeight);
mScaleAnimation.setupStartValues();
mScaleAnimation.start();
+ } else {
+ mCallback.setUserLockedChild(mCurrView, false);
}
- mCallback.setUserLockedChild(mCurrView, false);
mExpanding = false;
mExpansionStyle = NONE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index fdf4dbf..56f83df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -54,6 +54,7 @@ public class ExpandableNotificationRow extends FrameLayout
private boolean mMaxHeightNeedsUpdate;
private NotificationActivator mActivator;
private LatestItemView.OnActivatedListener mOnActivatedListener;
+ private boolean mSelfInitiatedLayout;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -162,6 +163,11 @@ public class ExpandableNotificationRow extends FrameLayout
}
private void updateMaxExpandHeight() {
+
+ // We don't want this method to trigger a layout of the whole view hierarchy,
+ // as the layout parameters in the end are the same which they were in the beginning.
+ // Otherwise a loop may occur if this method is called on the layout of a parent.
+ mSelfInitiatedLayout = true;
ViewGroup.LayoutParams lp = getLayoutParams();
int oldHeight = lp.height;
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -171,6 +177,14 @@ public class ExpandableNotificationRow extends FrameLayout
lp.height = oldHeight;
setLayoutParams(lp);
mMaxExpandHeight = getMeasuredHeight();
+ mSelfInitiatedLayout = false;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mSelfInitiatedLayout) {
+ super.requestLayout();
+ }
}
/**
@@ -257,4 +271,11 @@ public class ExpandableNotificationRow extends FrameLayout
public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) {
mLatestItemView.setBackgroundResourceIds(bgResId, dimmedBgResId);
}
+
+ /**
+ * @return the potential height this view could expand in addition.
+ */
+ public int getExpandPotential() {
+ return getMaximumAllowedExpandHeight() - getHeight();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 9800bc9..36d94a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
-import android.graphics.Outline;
import android.graphics.Paint;
import android.util.AttributeSet;
@@ -31,6 +30,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.widget.OverScroller;
import com.android.systemui.ExpandHelper;
@@ -55,7 +55,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private static final int INVALID_POINTER = -1;
private SwipeHelper mSwipeHelper;
- private boolean mSwipingInProgress = true;
+ private boolean mSwipingInProgress;
private int mCurrentStackHeight = Integer.MAX_VALUE;
private int mOwnScrollY;
private int mMaxLayoutHeight;
@@ -73,7 +73,6 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mSidePaddings;
private Paint mDebugPaint;
- private int mBackgroundRoundedRectCornerRadius;
private int mContentHeight;
private int mCollapsedSize;
private int mBottomStackPeekSize;
@@ -145,9 +144,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mSidePaddings = context.getResources()
.getDimensionPixelSize(R.dimen.notification_side_padding);
- mBackgroundRoundedRectCornerRadius = context.getResources()
- .getDimensionPixelSize(
- com.android.internal.R.dimen.notification_quantum_rounded_rect_radius);
mCollapsedSize = context.getResources()
.getDimensionPixelSize(R.dimen.notification_row_min_height);
mBottomStackPeekSize = context.getResources()
@@ -177,18 +173,23 @@ public class NotificationStackScrollLayout extends ViewGroup
View child = getChildAt(i);
float width = child.getMeasuredWidth();
float height = child.getMeasuredHeight();
- int oldWidth = child.getWidth();
- int oldHeight = child.getHeight();
child.layout((int) (centerX - width / 2.0f),
0,
(int) (centerX + width / 2.0f),
(int) height);
- updateChildOutline(child, width, height, oldWidth, oldHeight);
}
setMaxLayoutHeight(getHeight() - mEmptyMarginBottom);
- updateScrollPositionIfNecessary();
- updateChildren();
updateContentHeight();
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ updateScrollPositionIfNecessary();
+ updateChildren();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+
}
public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
@@ -227,7 +228,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mCurrentStackScrollState.setScrollY(mOwnScrollY);
mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState);
mCurrentStackScrollState.apply();
- mOwnScrollY = mCurrentStackScrollState.getScrollY();
if (mListener != null) {
mListener.onChildLocationsChanged(this);
}
@@ -240,31 +240,6 @@ public class NotificationStackScrollLayout extends ViewGroup
return false;
}
- private void updateChildOutline(View child,
- float width,
- float height,
- int oldWidth,
- int oldHeight) {
- // The children currently have paddings inside themselfs because of the expansion
- // visualization. In order for the shadows to work correctly we have to set the correct
- // outline.
- View container = child.findViewById(R.id.container);
- if (container != null && (oldWidth != width || oldHeight != height)) {
- Outline outline = getOutlineForSize(container.getLeft(),
- container.getTop(),
- container.getWidth(),
- container.getHeight());
- child.setOutline(outline);
- }
- }
-
- private Outline getOutlineForSize(int leftInset, int topInset, int width, int height) {
- Outline result = new Outline();
- result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height,
- mBackgroundRoundedRectCornerRadius);
- return result;
- }
-
private void updateScrollPositionIfNecessary() {
int scrollRange = getScrollRange();
if (scrollRange < mOwnScrollY) {
@@ -284,7 +259,7 @@ public class NotificationStackScrollLayout extends ViewGroup
*
* @return either the layout height or the externally defined height, whichever is smaller
*/
- private float getLayoutHeight() {
+ private int getLayoutHeight() {
return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
}
@@ -640,14 +615,48 @@ public class NotificationStackScrollLayout extends ViewGroup
private int getScrollRange() {
int scrollRange = 0;
- if (getChildCount() > 0) {
+ View firstChild = getFirstChildNotGone();
+ if (firstChild != null) {
int contentHeight = getContentHeight();
- scrollRange = Math.max(0,
- contentHeight - mMaxLayoutHeight + mBottomStackPeekSize);
+ int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
+ int firstChildExpandPotential = firstChildMaxExpandHeight - firstChild.getHeight();
+
+ // If we already scrolled in, the first child is layouted smaller than it actually
+ // could be when expanded. We have to compensate for this loss of the contentHeight
+ // by adding the expand potential again.
+ contentHeight += firstChildExpandPotential;
+ scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize);
+ if (scrollRange > 0 && getChildCount() > 0) {
+ // We want to at least be able collapse the first item and not ending in a weird
+ // end state.
+ scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
+ }
}
return scrollRange;
}
+ /**
+ * @return the first child which has visibility unequal to GONE
+ */
+ private View getFirstChildNotGone() {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ private int getMaxExpandHeight(View view) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ return row.getMaximumAllowedExpandHeight();
+ }
+ return view.getHeight();
+ }
+
private int getContentHeight() {
return mContentHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 431f6fe..a7363f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,13 +48,14 @@ public class StackScrollAlgorithm {
private StackIndentationFunctor mTopStackIndentationFunctor;
private StackIndentationFunctor mBottomStackIndentationFunctor;
- private float mLayoutHeight;
+ private int mLayoutHeight;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpansionChanging;
private int mFirstChildMaxHeight;
private boolean mIsExpanded;
private View mFirstChildWhileExpanding;
private boolean mExpandedOnStart;
+ private int mTopStackTotalSize;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -72,16 +74,16 @@ public class StackScrollAlgorithm {
mZDistanceBetweenElements = context.getResources()
.getDimensionPixelSize(R.dimen.z_distance_between_notifications);
mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
-
+ mTopStackTotalSize = mCollapsedSize + mPaddingBetweenElements;
mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
MAX_ITEMS_IN_TOP_STACK,
mTopStackPeekSize,
- mCollapsedSize + mPaddingBetweenElements,
+ mTopStackTotalSize,
0.5f);
mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
MAX_ITEMS_IN_BOTTOM_STACK,
mBottomStackPeekSize,
- mBottomStackPeekSize,
+ mCollapsedSize + mPaddingBetweenElements + mBottomStackPeekSize,
0.5f);
}
@@ -94,14 +96,17 @@ public class StackScrollAlgorithm {
// First we reset the view states to their default values.
resultState.resetViewStates();
- // The first element is always in there so it's initialized with 1.0f;
- algorithmState.itemsInTopStack = 1.0f;
+ algorithmState.itemsInTopStack = 0.0f;
algorithmState.partialInTop = 0.0f;
algorithmState.lastTopStackIndex = 0;
- algorithmState.scrollY = resultState.getScrollY();
+ algorithmState.scrolledPixelsTop = 0;
algorithmState.itemsInBottomStack = 0.0f;
+ algorithmState.partialInBottom = 0.0f;
+
updateVisibleChildren(resultState, algorithmState);
+ algorithmState.scrollY = getAlgorithmScrollPosition(resultState, algorithmState);
+
// Phase 1:
findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
@@ -110,9 +115,42 @@ public class StackScrollAlgorithm {
// Phase 3:
updateZValuesForState(resultState, algorithmState);
+ }
+
+ /**
+ * Calculates the scroll offset of the algorithm, based on the resultState.
+ *
+ * @param resultState the state to base the calculation on
+ * @param algorithmState The state in which the current pass of the algorithm is currently in
+ * @return the scroll offset used for the algorithm
+ */
+ private int getAlgorithmScrollPosition(StackScrollState resultState,
+ StackScrollAlgorithmState algorithmState) {
- // write the algorithm state to the result
- resultState.setScrollY(algorithmState.scrollY);
+ int resultScroll = resultState.getScrollY() + mCollapsedSize;
+
+ // If the first child was collapsed in an earlier pass, we have to decrease the scroll
+ // position to get into the same state again.
+ if (algorithmState.visibleChildren.size() > 0) {
+ View firstView = algorithmState.visibleChildren.get(0);
+ if (firstView instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow firstRow = (ExpandableNotificationRow) firstView;
+ if (firstRow.isUserLocked()) {
+ // User is currently modifying this height.
+ return resultScroll;
+ }
+ int scrolledInAmount = 0;
+ // If the child size was not decreased due to scrolling, we don't substract it,
+ if (!mIsExpansionChanging) {
+ scrolledInAmount = firstRow.getExpandPotential();
+ } else if (mExpandedOnStart && mFirstChildWhileExpanding == firstView) {
+ scrolledInAmount = firstRow.getMaximumAllowedExpandHeight() -
+ mFirstChildMaxHeight;
+ }
+ resultScroll -= scrolledInAmount;
+ }
+ }
+ return resultScroll;
}
/**
@@ -141,13 +179,12 @@ public class StackScrollAlgorithm {
*/
private void updatePositionsForState(StackScrollState resultState,
StackScrollAlgorithmState algorithmState) {
- float stackHeight = getLayoutHeight();
// The starting position of the bottom stack peek
- float bottomPeekStart = stackHeight - mBottomStackPeekSize;
+ float bottomPeekStart = mLayoutHeight - mBottomStackPeekSize;
// The position where the bottom stack starts.
- float transitioningPositionStart = bottomPeekStart - mCollapsedSize;
+ float bottomStackStart = bottomPeekStart - mCollapsedSize;
// The y coordinate of the current child.
float currentYPosition = 0.0f;
@@ -160,76 +197,110 @@ public class StackScrollAlgorithm {
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
- childViewState.yTranslation = currentYPosition;
childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN;
int childHeight = child.getHeight();
- // The y position after this element
- float nextYPosition = currentYPosition + childHeight + mPaddingBetweenElements;
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
- float scrollOffset = yPositionInScrollViewAfterElement - algorithmState.scrollY;
- if (i < algorithmState.lastTopStackIndex) {
+ float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize;
+
+ if (i == algorithmState.lastTopStackIndex + 1) {
+ // Normally the position of this child is the position in the regular scrollview,
+ // but if the two stacks are very close to each other,
+ // then have have to push it even more upwards to the position of the bottom
+ // stack start.
+ currentYPosition = Math.min(scrollOffset, bottomStackStart);
+ }
+ childViewState.yTranslation = currentYPosition;
+
+ // The y position after this element
+ float nextYPosition = currentYPosition + childHeight +
+ mPaddingBetweenElements;
+
+ if (i <= algorithmState.lastTopStackIndex) {
// Case 1:
// We are in the top Stack
- nextYPosition = updateStateForTopStackChild(algorithmState,
- numberOfElementsCompletelyIn,
- i, childViewState);
- } else if (i == algorithmState.lastTopStackIndex) {
+ updateStateForTopStackChild(algorithmState,
+ numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset);
+ clampYTranslation(childViewState, childHeight);
+ // check if we are overlapping with the bottom stack
+ if (childViewState.yTranslation + childHeight + mPaddingBetweenElements
+ >= bottomStackStart && !mIsExpansionChanging) {
+ // TODO: handle overlapping sizes with end stack better
+ // we just collapse this element
+ childViewState.height = mCollapsedSize;
+ }
+ } else if (nextYPosition >= bottomStackStart) {
// Case 2:
- // First element of regular scrollview comes next, so the position is just the
- // scrolling position
- nextYPosition = updateStateForFirstScrollingChild(transitioningPositionStart,
- childViewState, scrollOffset);
- } else if (nextYPosition >= transitioningPositionStart) {
- if (currentYPosition >= transitioningPositionStart) {
- // Case 3:
+ // We are in the bottom stack.
+ if (currentYPosition >= bottomStackStart) {
// According to the regular scroll view we are fully translated out of the
// bottom of the screen so we are fully in the bottom stack
- nextYPosition = updateStateForChildFullyInBottomStack(algorithmState,
- transitioningPositionStart, childViewState, childHeight);
+ updateStateForChildFullyInBottomStack(algorithmState,
+ bottomStackStart, childViewState, childHeight);
} else {
- // Case 4:
// According to the regular scroll view we are currently translating out of /
// into the bottom of the screen
- nextYPosition = updateStateForChildTransitioningInBottom(
- algorithmState, stackHeight, transitioningPositionStart,
- currentYPosition, childViewState,
- childHeight, nextYPosition);
+ updateStateForChildTransitioningInBottom(algorithmState,
+ bottomStackStart, bottomPeekStart, currentYPosition,
+ childViewState, childHeight);
}
} else {
+ // Case 3:
+ // We are in the regular scroll area.
childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
+ clampYTranslation(childViewState, childHeight);
}
+
// The first card is always rendered.
if (i == 0) {
childViewState.alpha = 1.0f;
+ childViewState.yTranslation = 0;
childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD;
}
if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
}
- nextYPosition = Math.max(0, nextYPosition);
- currentYPosition = nextYPosition;
+ currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
yPositionInScrollView = yPositionInScrollViewAfterElement;
}
}
/**
- * Update the state for the first child which is in the regular scrolling area.
+ * Clamp the yTranslation both up and down to valid positions.
*
- * @param transitioningPositionStart the transition starting position of the bottom stack
* @param childViewState the view state of the child
- * @param scrollOffset the position in the regular scroll view after this child
- * @return the next child position
+ * @param childHeight the height of this child
*/
- private float updateStateForFirstScrollingChild(float transitioningPositionStart,
- StackScrollState.ViewState childViewState, float scrollOffset) {
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
- if (scrollOffset < transitioningPositionStart) {
- return scrollOffset;
- } else {
- return transitioningPositionStart;
- }
+ private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) {
+ clampPositionToBottomStackStart(childViewState, childHeight);
+ clampPositionToTopStackEnd(childViewState, childHeight);
+ }
+
+ /**
+ * Clamp the yTranslation of the child down such that its end is at most on the beginning of
+ * the bottom stack.
+ *
+ * @param childViewState the view state of the child
+ * @param childHeight the height of this child
+ */
+ private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState,
+ int childHeight) {
+ childViewState.yTranslation = Math.min(childViewState.yTranslation,
+ mLayoutHeight - mBottomStackPeekSize - childHeight);
+ }
+
+ /**
+ * Clamp the yTranslation of the child up such that its end is at lest on the end of the top
+ * stack.
+ *
+ * @param childViewState the view state of the child
+ * @param childHeight the height of this child
+ */
+ private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState,
+ int childHeight) {
+ childViewState.yTranslation = Math.max(childViewState.yTranslation,
+ mCollapsedSize - childHeight);
}
private int getMaxAllowedChildHeight(View child) {
@@ -240,41 +311,35 @@ public class StackScrollAlgorithm {
return child.getHeight();
}
- private float updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
- float stackHeight, float transitioningPositionStart, float currentYPosition,
- StackScrollState.ViewState childViewState, int childHeight, float nextYPosition) {
- float newSize = transitioningPositionStart + mCollapsedSize - currentYPosition;
- newSize = Math.min(childHeight, newSize);
- // Transitioning element on top of bottom stack:
+ private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
+ float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
+ StackScrollState.ViewState childViewState, int childHeight) {
+
+ // This is the transitioning element on top of bottom stack, calculate how far we are in.
algorithmState.partialInBottom = 1.0f - (
- (stackHeight - mBottomStackPeekSize - nextYPosition) / mCollapsedSize);
- // Our element can be expanded, so we might even have to scroll further than
- // mCollapsedSize
- algorithmState.partialInBottom = Math.min(1.0f, algorithmState.partialInBottom);
- float offset = mBottomStackIndentationFunctor.getValue(
- algorithmState.partialInBottom);
- nextYPosition = transitioningPositionStart + offset;
+ (transitioningPositionStart - currentYPosition) / (childHeight +
+ mPaddingBetweenElements));
+
+ // the offset starting at the transitionPosition of the bottom stack
+ float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
- // TODO: only temporarily collapse
- if (childHeight != (int) newSize) {
- childViewState.height = (int) newSize;
- }
- childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
+ childViewState.yTranslation = transitioningPositionStart + offset - childHeight;
- return nextYPosition;
+ // We want at least to be at the end of the top stack when collapsing
+ clampPositionToTopStackEnd(childViewState, childHeight);
+ childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
}
- private float updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
+ private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
float transitioningPositionStart, StackScrollState.ViewState childViewState,
int childHeight) {
- float nextYPosition;
+ float currentYPosition;
algorithmState.itemsInBottomStack += 1.0f;
if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
// We are visually entering the bottom stack
- nextYPosition = transitioningPositionStart
- + mBottomStackIndentationFunctor.getValue(
- algorithmState.itemsInBottomStack);
+ currentYPosition = transitioningPositionStart
+ + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack);
childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING;
} else {
// we are fully inside the stack
@@ -285,43 +350,56 @@ public class StackScrollAlgorithm {
childViewState.alpha = 1.0f - algorithmState.partialInBottom;
}
childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN;
- nextYPosition = transitioningPositionStart + mBottomStackPeekSize;
- }
- // TODO: only temporarily collapse
- if (childHeight != mCollapsedSize) {
- childViewState.height = mCollapsedSize;
+ currentYPosition = mLayoutHeight;
}
- return nextYPosition;
+ childViewState.yTranslation = currentYPosition - childHeight;
+ clampPositionToTopStackEnd(childViewState, childHeight);
}
- private float updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
- int numberOfElementsCompletelyIn, int i, StackScrollState.ViewState childViewState) {
+ private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
+ int numberOfElementsCompletelyIn, int i, int childHeight,
+ StackScrollState.ViewState childViewState, float scrollOffset) {
- float nextYPosition = 0;
// First we calculate the index relative to the current stack window of size at most
// {@link #MAX_ITEMS_IN_TOP_STACK}
- int paddedIndex = i
+ int paddedIndex = i - 1
- Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0);
if (paddedIndex >= 0) {
+
// We are currently visually entering the top stack
- nextYPosition = mCollapsedSize + mPaddingBetweenElements -
- mTopStackIndentationFunctor.getValue(
- algorithmState.itemsInTopStack - i - 1);
- nextYPosition = Math.min(nextYPosition, mLayoutHeight - mCollapsedSize
- - mBottomStackPeekSize);
- if (paddedIndex == 0) {
- childViewState.alpha = 1.0f - algorithmState.partialInTop;
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN;
+ float distanceToStack = childHeight - algorithmState.scrolledPixelsTop;
+ if (i == algorithmState.lastTopStackIndex && distanceToStack > mTopStackTotalSize) {
+
+ // Child is currently translating into stack but not yet inside slow down zone.
+ // Handle it like the regular scrollview.
+ childViewState.yTranslation = scrollOffset;
} else {
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
+ // Apply stacking logic.
+ float numItemsBefore;
+ if (i == algorithmState.lastTopStackIndex) {
+ numItemsBefore = 1.0f - (distanceToStack / mTopStackTotalSize);
+ } else {
+ numItemsBefore = algorithmState.itemsInTopStack - i;
+ }
+ // The end position of the current child
+ float currentChildEndY = mCollapsedSize + mTopStackTotalSize -
+ mTopStackIndentationFunctor.getValue(numItemsBefore);
+ childViewState.yTranslation = currentChildEndY - childHeight;
}
+ childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
} else {
- // We are hidden behind the top card and faded out, so we can hide ourselves.
- childViewState.alpha = 0.0f;
+ if (paddedIndex == -1) {
+ childViewState.alpha = 1.0f - algorithmState.partialInTop;
+ } else {
+ // We are hidden behind the top card and faded out, so we can hide ourselves.
+ childViewState.alpha = 0.0f;
+ }
+ childViewState.yTranslation = mCollapsedSize - childHeight;
childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN;
}
- return nextYPosition;
+
+
}
/**
@@ -347,10 +425,22 @@ public class StackScrollAlgorithm {
+ childHeight
+ mPaddingBetweenElements;
if (yPositionInScrollView < algorithmState.scrollY) {
- if (yPositionInScrollViewAfterElement <= algorithmState.scrollY) {
+ if (i == 0 && algorithmState.scrollY == mCollapsedSize) {
+
+ // The starting position of the bottom stack peek
+ int bottomPeekStart = mLayoutHeight - mBottomStackPeekSize;
+ // Collapse and expand the first child while the shade is being expanded
+ float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
+ ? mFirstChildMaxHeight
+ : childHeight;
+ childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
+ mCollapsedSize);
+ algorithmState.itemsInTopStack = 1.0f;
+
+ } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
// According to the regular scroll view we are fully off screen
algorithmState.itemsInTopStack += 1.0f;
- if (childHeight != mCollapsedSize) {
+ if (i == 0) {
childViewState.height = mCollapsedSize;
}
} else {
@@ -360,45 +450,27 @@ public class StackScrollAlgorithm {
- mPaddingBetweenElements
- algorithmState.scrollY;
+ if (i == 0) {
+ newSize += mCollapsedSize;
+ }
+
// How much did we scroll into this child
- algorithmState.partialInTop = (mCollapsedSize - newSize) / (mCollapsedSize
+ algorithmState.scrolledPixelsTop = childHeight - newSize;
+ algorithmState.partialInTop = (algorithmState.scrolledPixelsTop) / (childHeight
+ mPaddingBetweenElements);
// Our element can be expanded, so this can get negative
algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop);
algorithmState.itemsInTopStack += algorithmState.partialInTop;
- // TODO: handle overlapping sizes with end stack
newSize = Math.max(mCollapsedSize, newSize);
- // TODO: only temporarily collapse
- if (newSize != childHeight) {
+ if (i == 0) {
childViewState.height = (int) newSize;
-
- // We decrease scrollY by the same amount we made this child smaller.
- // The new scroll position is therefore the start of the element
- algorithmState.scrollY = (int) yPositionInScrollView;
- resultState.setScrollY(algorithmState.scrollY);
- }
- if (childHeight > mCollapsedSize) {
- // If we are just resizing this child, this element is not treated to be
- // transitioning into the stack and therefore it is the last element in
- // the stack.
- algorithmState.lastTopStackIndex = i;
- break;
}
+ algorithmState.lastTopStackIndex = i;
+ break;
}
} else {
- algorithmState.lastTopStackIndex = i;
- if (i == 0) {
-
- // The starting position of the bottom stack peek
- float bottomPeekStart = getLayoutHeight() - mBottomStackPeekSize;
- // Collapse and expand the first child while the shade is being expanded
- float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
- ? mFirstChildMaxHeight
- : childHeight;
- childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
- mCollapsedSize);
- }
+ algorithmState.lastTopStackIndex = i - 1;
// We are already past the stack so we can end the loop
break;
}
@@ -435,11 +507,11 @@ public class StackScrollAlgorithm {
}
}
- public float getLayoutHeight() {
+ public int getLayoutHeight() {
return mLayoutHeight;
}
- public void setLayoutHeight(float layoutHeight) {
+ public void setLayoutHeight(int layoutHeight) {
this.mLayoutHeight = layoutHeight;
}
@@ -512,10 +584,12 @@ public class StackScrollAlgorithm {
public float partialInTop;
/**
+ * The number of pixels the last child in the top stack has scrolled in to the stack
+ */
+ public float scrolledPixelsTop;
+
+ /**
* The last item index which is in the top stack.
- * NOTE: In the top stack the item after the transitioning element is also in the stack!
- * This is needed to ensure a smooth transition between the y position in the regular
- * scrollview and the one in the stack.
*/
public int lastTopStackIndex;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 06a08f3..9742e65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.stack;
+import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import com.android.systemui.R;
+
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +38,10 @@ public class StackScrollState {
private final ViewGroup mHostView;
private Map<View, ViewState> mStateMap;
private int mScrollY;
+ private final Rect mClipRect = new Rect();
+ private int mBackgroundRoundedRectCornerRadius;
+ private final Outline mChildOutline = new Outline();
+ private final int mChildDividerHeight;
public int getScrollY() {
return mScrollY;
@@ -46,6 +54,10 @@ public class StackScrollState {
public StackScrollState(ViewGroup hostView) {
mHostView = hostView;
mStateMap = new HashMap<View, ViewState>();
+ mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_quantum_rounded_rect_radius);
+ mChildDividerHeight = hostView.getResources().getDimensionPixelSize(R.dimen
+ .notification_divider_height);
}
public ViewGroup getHostView() {
@@ -83,10 +95,17 @@ public class StackScrollState {
*/
public void apply() {
int numChildren = mHostView.getChildCount();
+ float previousNotificationEnd = 0;
+ float previousNotificationStart = 0;
for (int i = 0; i < numChildren; i++) {
View child = mHostView.getChildAt(i);
ViewState state = mStateMap.get(child);
- if (state != null) {
+ if (state == null) {
+ Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
+ "to the hostView");
+ continue;
+ }
+ if (!state.gone) {
float alpha = child.getAlpha();
float yTranslation = child.getTranslationY();
float zTranslation = child.getTranslationZ();
@@ -117,7 +136,7 @@ public class StackScrollState {
// apply visibility
int oldVisibility = child.getVisibility();
int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
- if (newVisibility != oldVisibility && !state.gone) {
+ if (newVisibility != oldVisibility) {
child.setVisibility(newVisibility);
}
@@ -135,13 +154,94 @@ public class StackScrollState {
if (height != newHeight) {
applyNewHeight(child, newHeight);
}
- } else {
- Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
- "to the hostView");
+
+ // apply clipping and shadow
+ float newNotificationEnd = newYTranslation + newHeight;
+ updateChildClippingAndShadow(child, newHeight,
+ newNotificationEnd - (previousNotificationEnd - mChildDividerHeight),
+ newHeight - (previousNotificationStart - newYTranslation));
+
+ previousNotificationStart = newYTranslation;
+ previousNotificationEnd = newNotificationEnd;
}
}
}
+ /**
+ * Updates the shadow outline and the clipping for a view.
+ *
+ * @param child the view to update
+ * @param realHeight the currently applied height of the view
+ * @param clipHeight the desired clip height, the rest of the view will be clipped from the top
+ * @param shadowHeight the desired height of the shadow, the shadow ends on the bottom
+ */
+ private void updateChildClippingAndShadow(View child, int realHeight, float clipHeight,
+ float shadowHeight) {
+ if (realHeight > shadowHeight) {
+ updateChildOutline(child, realHeight, shadowHeight);
+ } else {
+ updateChildOutline(child, realHeight, realHeight);
+ }
+ if (realHeight > clipHeight) {
+ updateChildClip(child, realHeight, clipHeight);
+ } else {
+ child.setClipBounds(null);
+ }
+ }
+
+ /**
+ * Updates the clipping of a view
+ *
+ * @param child the view to update
+ * @param height the currently applied height of the view
+ * @param clipHeight the desired clip height, the rest of the view will be clipped from the top
+ */
+ private void updateChildClip(View child, int height, float clipHeight) {
+ // The children currently have paddings inside themselfs because of the expansion
+ // visualization. In order for the clipping to work correctly we have to set the correct
+ // clip rect on the child.
+ View container = child.findViewById(R.id.container);
+ if (container != null) {
+ int clipInset = (int) (height - clipHeight);
+ mClipRect.set(0,
+ clipInset,
+ child.getWidth(),
+ height);
+ child.setClipBounds(mClipRect);
+ }
+ }
+
+ /**
+ * Updates the outline of a view
+ *
+ * @param child the view to update
+ * @param height the currently applied height of the view
+ * @param outlineHeight the desired height of the outline, the outline ends on the bottom
+ */
+ private void updateChildOutline(View child,
+ int height,
+ float outlineHeight) {
+ // The children currently have paddings inside themselfs because of the expansion
+ // visualization. In order for the shadows to work correctly we have to set the correct
+ // outline on the child.
+ View container = child.findViewById(R.id.container);
+ if (container != null) {
+ int shadowInset = (int) (height - outlineHeight);
+ getOutlineForSize(container.getLeft(),
+ container.getTop() + shadowInset,
+ container.getWidth(),
+ container.getHeight() - shadowInset,
+ mChildOutline);
+ child.setOutline(mChildOutline);
+ }
+ }
+
+ private void getOutlineForSize(int leftInset, int topInset, int width, int height,
+ Outline result) {
+ result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height,
+ mBackgroundRoundedRectCornerRadius);
+ }
+
private void applyNewHeight(View child, int newHeight) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
lp.height = newHeight;