summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSelim Cinek <cinek@google.com>2014-04-22 15:19:55 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2014-04-22 15:19:55 +0000
commitd304f2b0bb526d88041dd463d2194478362712c0 (patch)
tree28f08ed864a00a96ff97792ca9a36bedbaf8f04e
parent4f3693de49c5afdeccd236cba28436260220b6dd (diff)
parenta8fb4a2d00ddadd28c620d424f729eaad02654e0 (diff)
downloadframeworks_base-d304f2b0bb526d88041dd463d2194478362712c0.zip
frameworks_base-d304f2b0bb526d88041dd463d2194478362712c0.tar.gz
frameworks_base-d304f2b0bb526d88041dd463d2194478362712c0.tar.bz2
am a8fb4a2d: Merge "Avoiding intermediate states in NotificationStackScroller"
* commit 'a8fb4a2d00ddadd28c620d424f729eaad02654e0': Avoiding intermediate states in NotificationStackScroller
-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;