private void updateStateForChildFullyInBottomStack( StackScrollAlgorithmState algorithmState, float transitioningPositionStart, StackViewState childViewState, int childHeight, AmbientState ambientState) { Log.d(TAG, "updateStateForChildFullyInBottomStack: "); float currentYPosition; algorithmState.itemsInBottomStack += 1.0f; if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { // We are visually entering the bottom stack currentYPosition = transitioningPositionStart + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack) - mPaddingBetweenElements; childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING; } else { // we are fully inside the stack if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { childViewState.alpha = 0.0f; } else if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 1) { childViewState.alpha = 1.0f - algorithmState.partialInBottom; } childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN; currentYPosition = ambientState.getInnerHeight(); } childViewState.yTranslation = currentYPosition - 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( StackViewState childViewState, int childHeight, AmbientState ambientState) { Log.d(TAG, "clampPositionToBottomStackStart: "); childViewState.yTranslation = Math.min( childViewState.yTranslation, ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight); }
/** * Find the number of items in the top stack and update the result state if needed. * * @param resultState The result state to update if a height change of an child occurs * @param algorithmState The state in which the current pass of the algorithm is currently in */ private void findNumberOfItemsInTopStackAndUpdateState( StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState) { Log.d(TAG, "findNumberOfItemsInTopStackAndUpdateState: "); // The y Position if the element would be in a regular scrollView float yPositionInScrollView = 0.0f; int childCount = algorithmState.visibleChildren.size(); // find the number of elements in the top stack. for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); StackViewState childViewState = resultState.getViewStateForView(child); int childHeight = getMaxAllowedChildHeight(child, ambientState); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; if (yPositionInScrollView < algorithmState.scrollY) { if (i == 0 && algorithmState.scrollY <= mCollapsedSize) { // The starting position of the bottom stack peek int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding; // 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 (i == 0) { childViewState.height = mCollapsedSize; } } else { // According to the regular scroll view we are partially off screen // How much did we scroll into this child algorithmState.scrolledPixelsTop = algorithmState.scrollY - yPositionInScrollView; 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; if (i == 0) { // If it is expanded we have to collapse it to a new size float newSize = yPositionInScrollViewAfterElement - mPaddingBetweenElements - algorithmState.scrollY + mCollapsedSize; newSize = Math.max(mCollapsedSize, newSize); algorithmState.itemsInTopStack = 1.0f; childViewState.height = (int) newSize; } algorithmState.lastTopStackIndex = i; break; } } else { algorithmState.lastTopStackIndex = i - 1; // We are already past the stack so we can end the loop break; } yPositionInScrollView = yPositionInScrollViewAfterElement; } }
/** * Determine the positions for the views. This is the main part of the algorithm. * * @param resultState The result state to update if a change to the properties of a child occurs * @param algorithmState The state in which the current pass of the algorithm is currently in * @param ambientState The current ambient state */ private void updatePositionsForState( StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState) { Log.d(TAG, "updatePositionsForState: "); // The starting position of the bottom stack peek float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize; // The position where the bottom stack starts. float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength; // The y coordinate of the current child. float currentYPosition = 0.0f; // How far in is the element currently transitioning into the bottom stack. float yPositionInScrollView = 0.0f; // If we have a heads-up higher than the collapsed height we need to add the difference to // the padding of all other elements, i.e push in the top stack slightly. ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry(); int childCount = algorithmState.visibleChildren.size(); int numberOfElementsCompletelyIn = algorithmState.partialInTop == 1.0f ? algorithmState.lastTopStackIndex : (int) algorithmState.itemsInTopStack; for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); StackViewState childViewState = resultState.getViewStateForView(child); childViewState.location = StackViewState.LOCATION_UNKNOWN; int childHeight = getMaxAllowedChildHeight(child, ambientState); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; 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 updateStateForTopStackChild( algorithmState, numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset); clampPositionToTopStackEnd(childViewState, childHeight); // check if we are overlapping with the bottom stack if (childViewState.yTranslation + childHeight + mPaddingBetweenElements >= bottomStackStart && !mIsExpansionChanging && i != 0 && mIsSmallScreen) { // we just collapse this element slightly int newSize = (int) Math.max( bottomStackStart - mPaddingBetweenElements - childViewState.yTranslation, mCollapsedSize); childViewState.height = newSize; updateStateForChildTransitioningInBottom( algorithmState, bottomStackStart, bottomPeekStart, childViewState.yTranslation, childViewState, childHeight); } clampPositionToBottomStackStart(childViewState, childViewState.height, ambientState); } else if (nextYPosition >= bottomStackStart) { // Case 2: // 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 updateStateForChildFullyInBottomStack( algorithmState, bottomStackStart, childViewState, childHeight, ambientState); } else { // According to the regular scroll view we are currently translating out of / // into the bottom of the screen updateStateForChildTransitioningInBottom( algorithmState, bottomStackStart, bottomPeekStart, currentYPosition, childViewState, childHeight); } } else { // Case 3: // We are in the regular scroll area. childViewState.location = StackViewState.LOCATION_MAIN_AREA; clampYTranslation(childViewState, childHeight, ambientState); } // The first card is always rendered. if (i == 0) { childViewState.alpha = 1.0f; childViewState.yTranslation = Math.max(mCollapsedSize - algorithmState.scrollY, 0); if (childViewState.yTranslation + childViewState.height > bottomPeekStart - mCollapseSecondCardPadding) { childViewState.height = (int) Math.max( bottomPeekStart - mCollapseSecondCardPadding - childViewState.yTranslation, mCollapsedSize); } childViewState.location = StackViewState.LOCATION_FIRST_CARD; } if (childViewState.location == StackViewState.LOCATION_UNKNOWN) { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); } currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; yPositionInScrollView = yPositionInScrollViewAfterElement; if (ambientState.isShadeExpanded() && topHeadsUpEntry != null && child != topHeadsUpEntry) { childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize; } childViewState.yTranslation += ambientState.getTopPadding() + ambientState.getStackTranslation(); } updateHeadsUpStates(resultState, algorithmState, ambientState); }