Merge "Avoiding intermediate states in NotificationStackScroller"
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// write the algorithm state to the result
|
||||
resultState.setScrollY(algorithmState.scrollY);
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
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:
|
||||
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;
|
||||
algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
|
||||
// TODO: only temporarily collapse
|
||||
if (childHeight != (int) newSize) {
|
||||
childViewState.height = (int) newSize;
|
||||
}
|
||||
childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
|
||||
private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
|
||||
float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
|
||||
StackScrollState.ViewState childViewState, int childHeight) {
|
||||
|
||||
return nextYPosition;
|
||||
// This is the transitioning element on top of bottom stack, calculate how far we are in.
|
||||
algorithmState.partialInBottom = 1.0f - (
|
||||
(transitioningPositionStart - currentYPosition) / (childHeight +
|
||||
mPaddingBetweenElements));
|
||||
|
||||
// the offset starting at the transitionPosition of the bottom stack
|
||||
float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
|
||||
algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
|
||||
childViewState.yTranslation = transitioningPositionStart + offset - childHeight;
|
||||
|
||||
// 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;
|
||||
currentYPosition = mLayoutHeight;
|
||||
}
|
||||
// TODO: only temporarily collapse
|
||||
if (childHeight != mCollapsedSize) {
|
||||
childViewState.height = mCollapsedSize;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -511,11 +583,13 @@ 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user