コード例 #1
0
  /** Handle the special state when views are being dragged */
  private void handleDraggedViews(
      AmbientState ambientState,
      StackScrollState resultState,
      StackScrollAlgorithmState algorithmState) {
    Log.d(TAG, "handleDraggedViews: ");
    ArrayList<View> draggedViews = ambientState.getDraggedViews();
    for (View draggedView : draggedViews) {
      int childIndex = algorithmState.visibleChildren.indexOf(draggedView);
      if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) {
        View nextChild = algorithmState.visibleChildren.get(childIndex + 1);
        if (!draggedViews.contains(nextChild)) {
          // only if the view is not dragged itself we modify its state to be fully
          // visible
          StackViewState viewState = resultState.getViewStateForView(nextChild);
          // The child below the dragged one must be fully visible
          if (ambientState.isShadeExpanded()) {
            viewState.alpha = 1;
          }
        }

        // Lets set the alpha to the one it currently has, as its currently being dragged
        StackViewState viewState = resultState.getViewStateForView(draggedView);
        // The dragged child should keep the set alpha
        viewState.alpha = draggedView.getAlpha();
      }
    }
  }
コード例 #2
0
  private void updateClipping(
      StackScrollState resultState,
      StackScrollAlgorithmState algorithmState,
      AmbientState ambientState) {
    Log.d(TAG, "updateClipping: ");
    boolean dismissAllInProgress = ambientState.isDismissAllInProgress();
    float previousNotificationEnd = 0;
    float previousNotificationStart = 0;
    boolean previousNotificationIsSwiped = false;
    int childCount = algorithmState.visibleChildren.size();
    for (int i = 0; i < childCount; i++) {
      ExpandableView child = algorithmState.visibleChildren.get(i);
      StackViewState state = resultState.getViewStateForView(child);
      float newYTranslation = state.yTranslation + state.height * (1f - state.scale) / 2f;
      float newHeight = state.height * state.scale;
      // apply clipping and shadow
      float newNotificationEnd = newYTranslation + newHeight;

      float clipHeight;
      if (previousNotificationIsSwiped) {
        // When the previous notification is swiped, we don't clip the content to the
        // bottom of it.
        clipHeight = newHeight;
      } else {
        clipHeight = newNotificationEnd - previousNotificationEnd;
        clipHeight = Math.max(0.0f, clipHeight);
        if (clipHeight != 0.0f) {

          // In the unlocked shade we have to clip a little bit higher because of the rounded
          // corners of the notifications, but only if we are not fully overlapped by
          // the top card.
          float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius * state.scale;
          clipHeight += clippingCorrection;
        }
      }

      updateChildClippingAndBackground(
          state, newHeight, clipHeight, newHeight - (previousNotificationStart - newYTranslation));

      if (dismissAllInProgress) {
        state.clipTopAmount = Math.max(child.getMinClipTopAmount(), state.clipTopAmount);
      }

      if (!child.isTransparent()) {
        // Only update the previous values if we are not transparent,
        // otherwise we would clip to a transparent view.
        if ((dismissAllInProgress && canChildBeDismissed(child))) {
          previousNotificationIsSwiped = true;
        } else {
          previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
          previousNotificationEnd = newNotificationEnd;
          previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale;
        }
      }
    }
  }
コード例 #3
0
 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);
 }
コード例 #4
0
 /**
  * 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);
 }
コード例 #5
0
  public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
    // The state of the local variables are saved in an algorithmState to easily subdivide it
    // into multiple phases.
    Log.d(TAG, "getStackScrollState: ");
    StackScrollAlgorithmState algorithmState = mTempAlgorithmState;

    // First we reset the view states to their default values.
    resultState.resetViewStates();

    algorithmState.itemsInTopStack = 0.0f;
    algorithmState.partialInTop = 0.0f;
    algorithmState.lastTopStackIndex = 0;
    algorithmState.scrolledPixelsTop = 0;
    algorithmState.itemsInBottomStack = 0.0f;
    algorithmState.partialInBottom = 0.0f;
    float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);

    int scrollY = ambientState.getScrollY();

    // Due to the overScroller, the stackscroller can have negative scroll state. This is
    // already accounted for by the top padding and doesn't need an additional adaption
    scrollY = Math.max(0, scrollY);
    algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll);

    updateVisibleChildren(resultState, algorithmState);

    // Phase 1:
    findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState);

    // Phase 2:
    updatePositionsForState(resultState, algorithmState, ambientState);

    // Phase 3:
    updateZValuesForState(resultState, algorithmState);

    handleDraggedViews(ambientState, resultState, algorithmState);
    updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
    updateClipping(resultState, algorithmState, ambientState);
    updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
    getNotificationChildrenStates(resultState, algorithmState);
  }
コード例 #6
0
 /** Updates the dimmed, activated and hiding sensitive states of the children. */
 private void updateDimmedActivatedHideSensitive(
     AmbientState ambientState,
     StackScrollState resultState,
     StackScrollAlgorithmState algorithmState) {
   Log.d(TAG, "updateDimmedActivatedHideSensitive: ");
   boolean dimmed = ambientState.isDimmed();
   boolean dark = ambientState.isDark();
   boolean hideSensitive = ambientState.isHideSensitive();
   View activatedChild = ambientState.getActivatedChild();
   int childCount = algorithmState.visibleChildren.size();
   for (int i = 0; i < childCount; i++) {
     View child = algorithmState.visibleChildren.get(i);
     StackViewState childViewState = resultState.getViewStateForView(child);
     childViewState.dimmed = dimmed;
     childViewState.dark = dark;
     childViewState.hideSensitive = hideSensitive;
     boolean isActivatedChild = activatedChild == child;
     childViewState.scale = !mScaleDimmed || !dimmed || isActivatedChild ? 1.0f : DIMMED_SCALE;
     if (dimmed && isActivatedChild) {
       childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
     }
   }
 }
コード例 #7
0
 private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
   Log.d(TAG, "getMaxAllowedChildHeight: ");
   if (child instanceof ExpandableNotificationRow) {
     ExpandableNotificationRow row = (ExpandableNotificationRow) child;
     if (ambientState == null && row.isHeadsUp()
         || ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
       int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
       return mCollapsedSize + extraSize;
     }
     return row.getIntrinsicHeight();
   } else if (child instanceof ExpandableView) {
     ExpandableView expandableView = (ExpandableView) child;
     return expandableView.getIntrinsicHeight();
   }
   return child == null ? mCollapsedSize : child.getHeight();
 }
コード例 #8
0
 private void updateHeadsUpStates(
     StackScrollState resultState,
     StackScrollAlgorithmState algorithmState,
     AmbientState ambientState) {
   Log.d(TAG, "updateHeadsUpStates: ");
   int childCount = algorithmState.visibleChildren.size();
   ExpandableNotificationRow topHeadsUpEntry = null;
   for (int i = 0; i < childCount; i++) {
     View child = algorithmState.visibleChildren.get(i);
     if (!(child instanceof ExpandableNotificationRow)) {
       break;
     }
     ExpandableNotificationRow row = (ExpandableNotificationRow) child;
     if (!row.isHeadsUp()) {
       break;
     } else if (topHeadsUpEntry == null) {
       topHeadsUpEntry = row;
     }
     StackViewState childState = resultState.getViewStateForView(row);
     boolean isTopEntry = topHeadsUpEntry == row;
     if (mIsExpanded) {
       if (isTopEntry) {
         childState.height += row.getHeadsUpHeight() - mCollapsedSize;
       }
       childState.height = Math.max(childState.height, row.getHeadsUpHeight());
       // Ensure that the heads up is always visible even when scrolled off from the bottom
       float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
       childState.yTranslation = Math.min(childState.yTranslation, bottomPosition);
     }
     if (row.isPinned()) {
       childState.yTranslation = Math.max(childState.yTranslation, mNotificationsTopPadding);
       childState.height = row.getHeadsUpHeight();
       if (!isTopEntry) {
         // Ensure that a headsUp doesn't vertically extend further than the heads-up at
         // the top most z-position
         StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
         childState.height = row.getHeadsUpHeight();
         childState.yTranslation = topState.yTranslation + topState.height - childState.height;
       }
     }
   }
 }
コード例 #9
0
  /**
   * 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;
    }
  }
コード例 #10
0
  /**
   * 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);
  }