StackScrollAlgorithm.java revision 281c202784fe6eecab4cc535461f1b12c85b2cc0
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.systemui.statusbar.stack; 18 19import android.content.Context; 20import android.util.Log; 21import android.view.View; 22import android.view.ViewGroup; 23 24import com.android.systemui.R; 25import com.android.systemui.statusbar.ExpandableNotificationRow; 26import com.android.systemui.statusbar.ExpandableView; 27import com.android.systemui.statusbar.NotificationShelf; 28import com.android.systemui.statusbar.notification.FakeShadowView; 29import com.android.systemui.statusbar.notification.NotificationUtils; 30 31import java.util.ArrayList; 32import java.util.HashMap; 33import java.util.List; 34 35/** 36 * The Algorithm of the {@link com.android.systemui.statusbar.stack 37 * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar 38 * .stack.StackScrollState} 39 */ 40public class StackScrollAlgorithm { 41 42 private static final String LOG_TAG = "StackScrollAlgorithm"; 43 44 public static final int MAX_ITEMS_IN_BOTTOM_STACK = 3; 45 46 private int mPaddingBetweenElements; 47 private int mIncreasedPaddingBetweenElements; 48 private int mCollapsedSize; 49 private int mBottomStackPeekSize; 50 51 private StackIndentationFunctor mBottomStackIndentationFunctor; 52 53 private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); 54 private boolean mIsExpanded; 55 private int mBottomStackSlowDownLength; 56 57 public StackScrollAlgorithm(Context context) { 58 initView(context); 59 } 60 61 public void initView(Context context) { 62 initConstants(context); 63 } 64 65 public int getBottomStackSlowDownLength() { 66 return mBottomStackSlowDownLength + mPaddingBetweenElements; 67 } 68 69 private void initConstants(Context context) { 70 mPaddingBetweenElements = Math.max(1, context.getResources() 71 .getDimensionPixelSize(R.dimen.notification_divider_height)); 72 mIncreasedPaddingBetweenElements = context.getResources() 73 .getDimensionPixelSize(R.dimen.notification_divider_height_increased); 74 mCollapsedSize = context.getResources() 75 .getDimensionPixelSize(R.dimen.notification_min_height); 76 mBottomStackPeekSize = context.getResources() 77 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 78 mBottomStackSlowDownLength = context.getResources() 79 .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); 80 mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( 81 MAX_ITEMS_IN_BOTTOM_STACK, 82 mBottomStackPeekSize, 83 getBottomStackSlowDownLength(), 84 0.5f); 85 } 86 87 public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) { 88 // The state of the local variables are saved in an algorithmState to easily subdivide it 89 // into multiple phases. 90 StackScrollAlgorithmState algorithmState = mTempAlgorithmState; 91 92 // First we reset the view states to their default values. 93 resultState.resetViewStates(); 94 95 initAlgorithmState(resultState, algorithmState, ambientState); 96 97 updatePositionsForState(resultState, algorithmState, ambientState); 98 99 updateZValuesForState(resultState, algorithmState, ambientState); 100 101 updateHeadsUpStates(resultState, algorithmState, ambientState); 102 103 handleDraggedViews(ambientState, resultState, algorithmState); 104 updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState); 105 updateClipping(resultState, algorithmState, ambientState); 106 updateShelfState(resultState, algorithmState, ambientState); 107 getNotificationChildrenStates(resultState, algorithmState); 108 } 109 110 private void getNotificationChildrenStates(StackScrollState resultState, 111 StackScrollAlgorithmState algorithmState) { 112 int childCount = algorithmState.visibleChildren.size(); 113 for (int i = 0; i < childCount; i++) { 114 ExpandableView v = algorithmState.visibleChildren.get(i); 115 if (v instanceof ExpandableNotificationRow) { 116 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 117 row.getChildrenStates(resultState); 118 } 119 } 120 } 121 122 private void updateShelfState(StackScrollState resultState, 123 StackScrollAlgorithmState algorithmState, AmbientState ambientState) { 124 int childCount = algorithmState.visibleChildren.size(); 125 int shelfIndex = ambientState.getShelfIndex(); 126 for (int i = 0; i < childCount; i++) { 127 View child = algorithmState.visibleChildren.get(i); 128 ExpandableViewState childViewState = resultState.getViewStateForView(child); 129 130 // The speed bump can also be gone, so equality needs to be taken when comparing 131 // indices. 132 childViewState.belowShelf = shelfIndex != -1 && i >= shelfIndex; 133 } 134 NotificationShelf shelf = ambientState.getShelf(); 135 shelf.updateState(resultState, algorithmState, ambientState); 136 } 137 138 private void updateClipping(StackScrollState resultState, 139 StackScrollAlgorithmState algorithmState, AmbientState ambientState) { 140 float drawStart = ambientState.getTopPadding() + ambientState.getStackTranslation(); 141 float previousNotificationEnd = 0; 142 float previousNotificationStart = 0; 143 int childCount = algorithmState.visibleChildren.size(); 144 for (int i = 0; i < childCount; i++) { 145 ExpandableView child = algorithmState.visibleChildren.get(i); 146 ExpandableViewState state = resultState.getViewStateForView(child); 147 if (!child.mustStayOnScreen()) { 148 previousNotificationEnd = Math.max(drawStart, previousNotificationEnd); 149 previousNotificationStart = Math.max(drawStart, previousNotificationStart); 150 } 151 float newYTranslation = state.yTranslation; 152 float newHeight = state.height; 153 float newNotificationEnd = newYTranslation + newHeight; 154 boolean isHeadsUp = (child instanceof ExpandableNotificationRow) 155 && ((ExpandableNotificationRow) child).isPinned(); 156 if (newYTranslation < previousNotificationEnd 157 && (!isHeadsUp || ambientState.isShadeExpanded())) { 158 // The previous view is overlapping on top, clip! 159 float overlapAmount = previousNotificationEnd - newYTranslation; 160 state.clipTopAmount = (int) overlapAmount; 161 } else { 162 state.clipTopAmount = 0; 163 } 164 165 if (!child.isTransparent()) { 166 // Only update the previous values if we are not transparent, 167 // otherwise we would clip to a transparent view. 168 previousNotificationEnd = newNotificationEnd; 169 previousNotificationStart = newYTranslation; 170 } 171 } 172 } 173 174 public static boolean canChildBeDismissed(View v) { 175 if (!(v instanceof ExpandableNotificationRow)) { 176 return false; 177 } 178 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 179 if (row.areGutsExposed()) { 180 return false; 181 } 182 return row.canViewBeDismissed(); 183 } 184 185 /** 186 * Updates the dimmed, activated and hiding sensitive states of the children. 187 */ 188 private void updateDimmedActivatedHideSensitive(AmbientState ambientState, 189 StackScrollState resultState, StackScrollAlgorithmState algorithmState) { 190 boolean dimmed = ambientState.isDimmed(); 191 boolean dark = ambientState.isDark(); 192 boolean hideSensitive = ambientState.isHideSensitive(); 193 View activatedChild = ambientState.getActivatedChild(); 194 int childCount = algorithmState.visibleChildren.size(); 195 for (int i = 0; i < childCount; i++) { 196 View child = algorithmState.visibleChildren.get(i); 197 ExpandableViewState childViewState = resultState.getViewStateForView(child); 198 childViewState.dimmed = dimmed; 199 childViewState.dark = dark; 200 childViewState.hideSensitive = hideSensitive; 201 boolean isActivatedChild = activatedChild == child; 202 if (dimmed && isActivatedChild) { 203 childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements(); 204 } 205 } 206 } 207 208 /** 209 * Handle the special state when views are being dragged 210 */ 211 private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState, 212 StackScrollAlgorithmState algorithmState) { 213 ArrayList<View> draggedViews = ambientState.getDraggedViews(); 214 for (View draggedView : draggedViews) { 215 int childIndex = algorithmState.visibleChildren.indexOf(draggedView); 216 if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) { 217 View nextChild = algorithmState.visibleChildren.get(childIndex + 1); 218 if (!draggedViews.contains(nextChild)) { 219 // only if the view is not dragged itself we modify its state to be fully 220 // visible 221 ExpandableViewState viewState = resultState.getViewStateForView( 222 nextChild); 223 // The child below the dragged one must be fully visible 224 if (ambientState.isShadeExpanded()) { 225 viewState.shadowAlpha = 1; 226 viewState.hidden = false; 227 } 228 } 229 230 // Lets set the alpha to the one it currently has, as its currently being dragged 231 ExpandableViewState viewState = resultState.getViewStateForView(draggedView); 232 // The dragged child should keep the set alpha 233 viewState.alpha = draggedView.getAlpha(); 234 } 235 } 236 } 237 238 /** 239 * Initialize the algorithm state like updating the visible children. 240 */ 241 private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state, 242 AmbientState ambientState) { 243 state.itemsInBottomStack = 0.0f; 244 state.partialInBottom = 0.0f; 245 float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */); 246 247 int scrollY = ambientState.getScrollY(); 248 249 // Due to the overScroller, the stackscroller can have negative scroll state. This is 250 // already accounted for by the top padding and doesn't need an additional adaption 251 scrollY = Math.max(0, scrollY); 252 state.scrollY = (int) (scrollY + bottomOverScroll); 253 254 //now init the visible children and update paddings 255 ViewGroup hostView = resultState.getHostView(); 256 int childCount = hostView.getChildCount(); 257 state.visibleChildren.clear(); 258 state.visibleChildren.ensureCapacity(childCount); 259 state.increasedPaddingMap.clear(); 260 int notGoneIndex = 0; 261 ExpandableView lastView = null; 262 for (int i = 0; i < childCount; i++) { 263 ExpandableView v = (ExpandableView) hostView.getChildAt(i); 264 if (v.getVisibility() != View.GONE) { 265 if (v == ambientState.getShelf()) { 266 continue; 267 } 268 notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v); 269 float increasedPadding = v.getIncreasedPaddingAmount(); 270 if (increasedPadding != 0.0f) { 271 state.increasedPaddingMap.put(v, increasedPadding); 272 if (lastView != null) { 273 Float prevValue = state.increasedPaddingMap.get(lastView); 274 float newValue = prevValue != null 275 ? Math.max(prevValue, increasedPadding) 276 : increasedPadding; 277 state.increasedPaddingMap.put(lastView, newValue); 278 } 279 } 280 if (v instanceof ExpandableNotificationRow) { 281 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 282 283 // handle the notgoneIndex for the children as well 284 List<ExpandableNotificationRow> children = 285 row.getNotificationChildren(); 286 if (row.isSummaryWithChildren() && children != null) { 287 for (ExpandableNotificationRow childRow : children) { 288 if (childRow.getVisibility() != View.GONE) { 289 ExpandableViewState childState 290 = resultState.getViewStateForView(childRow); 291 childState.notGoneIndex = notGoneIndex; 292 notGoneIndex++; 293 } 294 } 295 } 296 } 297 lastView = v; 298 } 299 } 300 } 301 302 private int updateNotGoneIndex(StackScrollState resultState, 303 StackScrollAlgorithmState state, int notGoneIndex, 304 ExpandableView v) { 305 ExpandableViewState viewState = resultState.getViewStateForView(v); 306 viewState.notGoneIndex = notGoneIndex; 307 state.visibleChildren.add(v); 308 notGoneIndex++; 309 return notGoneIndex; 310 } 311 312 /** 313 * Determine the positions for the views. This is the main part of the algorithm. 314 * 315 * @param resultState The result state to update if a change to the properties of a child occurs 316 * @param algorithmState The state in which the current pass of the algorithm is currently in 317 * @param ambientState The current ambient state 318 */ 319 private void updatePositionsForState(StackScrollState resultState, 320 StackScrollAlgorithmState algorithmState, AmbientState ambientState) { 321 322 // The starting position of the bottom stack peek 323 float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize; 324 325 // The position where the bottom stack starts. 326 float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength; 327 328 // The y coordinate of the current child. 329 float currentYPosition = -algorithmState.scrollY; 330 int childCount = algorithmState.visibleChildren.size(); 331 for (int i = 0; i < childCount; i++) { 332 currentYPosition = updateChild(i, resultState, algorithmState, ambientState, 333 currentYPosition, bottomStackStart); 334 } 335 } 336 337 protected float updateChild(int i, StackScrollState resultState, 338 StackScrollAlgorithmState algorithmState, AmbientState ambientState, 339 float currentYPosition, float bottomStackStart) { 340 ExpandableView child = algorithmState.visibleChildren.get(i); 341 ExpandableViewState childViewState = resultState.getViewStateForView(child); 342 childViewState.location = ExpandableViewState.LOCATION_UNKNOWN; 343 int paddingAfterChild = getPaddingAfterChild(algorithmState, child); 344 int childHeight = getMaxAllowedChildHeight(child); 345 int collapsedHeight = child.getCollapsedHeight(); 346 childViewState.yTranslation = currentYPosition; 347 if (i == 0) { 348 updateFirstChildHeight(child, childViewState, childHeight, ambientState); 349 } 350 int shelfIndex = ambientState.getShelfIndex(); 351 boolean belowShelf = shelfIndex != -1 && i >= shelfIndex; 352 353 // The y position after this element 354 float nextYPosition = currentYPosition + childHeight + 355 paddingAfterChild; 356 if (nextYPosition >= bottomStackStart && belowShelf) { 357 // Case 1: 358 // We are in the bottom stack. 359 if (currentYPosition >= bottomStackStart) { 360 // According to the regular scroll view we are fully translated out of the 361 // bottom of the screen so we are fully in the bottom stack 362 updateStateForChildFullyInBottomStack(algorithmState, 363 bottomStackStart, childViewState, collapsedHeight, ambientState, child); 364 } else { 365 // According to the regular scroll view we are currently translating out of / 366 // into the bottom of the screen 367 updateStateForChildTransitioningInBottom(algorithmState, 368 bottomStackStart, child, currentYPosition, 369 childViewState, childHeight); 370 } 371 } else { 372 // Case 2: 373 // We are in the regular scroll area. 374 childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; 375 if (belowShelf) { 376 clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight, 377 ambientState); 378 } else { 379 clampPositionToShelf(i, childViewState, ambientState); 380 } 381 } 382 383 if (i == 0 && ambientState.getScrollY() <= 0) { 384 // The first card can get into the bottom stack if it's the only one 385 // on the lockscreen which pushes it up. Let's make sure that doesn't happen and 386 // it stays at the top 387 childViewState.yTranslation = Math.max(0, childViewState.yTranslation); 388 } 389 currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild; 390 if (currentYPosition <= 0) { 391 childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; 392 } 393 if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { 394 Log.wtf(LOG_TAG, "Failed to assign location for child " + i); 395 } 396 397 childViewState.yTranslation += ambientState.getTopPadding() 398 + ambientState.getStackTranslation(); 399 return currentYPosition; 400 } 401 402 protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState, 403 ExpandableView child) { 404 return algorithmState.getPaddingAfterChild(child); 405 } 406 407 private void updateHeadsUpStates(StackScrollState resultState, 408 StackScrollAlgorithmState algorithmState, AmbientState ambientState) { 409 int childCount = algorithmState.visibleChildren.size(); 410 ExpandableNotificationRow topHeadsUpEntry = null; 411 for (int i = 0; i < childCount; i++) { 412 View child = algorithmState.visibleChildren.get(i); 413 if (!(child instanceof ExpandableNotificationRow)) { 414 break; 415 } 416 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 417 if (!row.isHeadsUp()) { 418 break; 419 } 420 ExpandableViewState childState = resultState.getViewStateForView(row); 421 if (topHeadsUpEntry == null) { 422 topHeadsUpEntry = row; 423 childState.location = ExpandableViewState.LOCATION_FIRST_HUN; 424 } 425 boolean isTopEntry = topHeadsUpEntry == row; 426 float unmodifiedEndLocation = childState.yTranslation + childState.height; 427 if (mIsExpanded) { 428 // Ensure that the heads up is always visible even when scrolled off 429 clampHunToTop(ambientState, row, childState); 430 clampHunToMaxTranslation(ambientState, row, childState); 431 } 432 if (row.isPinned()) { 433 childState.yTranslation = Math.max(childState.yTranslation, 0); 434 childState.height = Math.max(row.getIntrinsicHeight(), childState.height); 435 ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry); 436 if (!isTopEntry && (!mIsExpanded 437 || unmodifiedEndLocation < topState.yTranslation + topState.height)) { 438 // Ensure that a headsUp doesn't vertically extend further than the heads-up at 439 // the top most z-position 440 childState.height = row.getIntrinsicHeight(); 441 childState.yTranslation = topState.yTranslation + topState.height 442 - childState.height; 443 } 444 } 445 } 446 } 447 448 private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row, 449 ExpandableViewState childState) { 450 float newTranslation = Math.max(ambientState.getTopPadding() 451 + ambientState.getStackTranslation(), childState.yTranslation); 452 childState.height = (int) Math.max(childState.height - (newTranslation 453 - childState.yTranslation), row.getCollapsedHeight()); 454 childState.yTranslation = newTranslation; 455 } 456 457 private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row, 458 ExpandableViewState childState) { 459 float newTranslation; 460 float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getCollapsedHeight(); 461 newTranslation = Math.min(childState.yTranslation, bottomPosition); 462 childState.height = (int) Math.max(childState.height 463 - (childState.yTranslation - newTranslation), row.getCollapsedHeight()); 464 childState.yTranslation = newTranslation; 465 } 466 467 /** 468 * Clamp the yTranslation of the child down such that its end is at most on the beginning of 469 * the bottom stack. 470 * 471 * @param childViewState the view state of the child 472 * @param childHeight the height of this child 473 * @param minHeight the minumum Height of the View 474 */ 475 private void clampPositionToBottomStackStart(ExpandableViewState childViewState, 476 int childHeight, int minHeight, AmbientState ambientState) { 477 478 int bottomStackStart = ambientState.getInnerHeight() 479 - mBottomStackPeekSize - mBottomStackSlowDownLength; 480 int childStart = bottomStackStart - childHeight; 481 if (childStart < childViewState.yTranslation) { 482 float newHeight = bottomStackStart - childViewState.yTranslation; 483 if (newHeight < minHeight) { 484 newHeight = minHeight; 485 childViewState.yTranslation = bottomStackStart - minHeight; 486 } 487 childViewState.height = (int) newHeight; 488 } 489 } 490 491 /** 492 * Clamp the height of the child down such that its end is at most on the beginning of 493 * the shelf. 494 * 495 * @param index the index of the view 496 * @param childViewState the view state of the child 497 * @param ambientState the ambient state 498 */ 499 private void clampPositionToShelf(int index, ExpandableViewState childViewState, 500 AmbientState ambientState) { 501 int minHeight = ambientState.getShelf().getNotificationMergeSize(); 502 int shelfEnd = ambientState.getInnerHeight(); 503 int shelfStart = shelfEnd - ambientState.getShelf().getIntrinsicHeight(); 504 int maxChildEnd = shelfEnd; 505 if (index != ambientState.getShelfIndex() - 1) { 506 maxChildEnd = shelfStart - mPaddingBetweenElements; 507 } 508 childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart); 509 if (childViewState.yTranslation + childViewState.height > maxChildEnd) { 510 float newHeight = maxChildEnd - childViewState.yTranslation; 511 if (newHeight < minHeight) { 512 newHeight = Math.min(minHeight, shelfEnd - childViewState.yTranslation); 513 } 514 childViewState.height = (int) newHeight; 515 } 516 } 517 518 protected int getMaxAllowedChildHeight(View child) { 519 if (child instanceof ExpandableView) { 520 ExpandableView expandableView = (ExpandableView) child; 521 return expandableView.getIntrinsicHeight(); 522 } 523 return child == null? mCollapsedSize : child.getHeight(); 524 } 525 526 private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, 527 float transitioningPositionStart, ExpandableView child, float currentYPosition, 528 ExpandableViewState childViewState, int childHeight) { 529 530 // This is the transitioning element on top of bottom stack, calculate how far we are in. 531 algorithmState.partialInBottom = 1.0f - ( 532 (transitioningPositionStart - currentYPosition) / (childHeight + 533 getPaddingAfterChild(algorithmState, child))); 534 535 // the offset starting at the transitionPosition of the bottom stack 536 float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom); 537 algorithmState.itemsInBottomStack += algorithmState.partialInBottom; 538 int newHeight = childHeight; 539 if (childHeight > child.getCollapsedHeight()) { 540 newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset - 541 getPaddingAfterChild(algorithmState, child) - currentYPosition, childHeight), 542 child.getCollapsedHeight()); 543 childViewState.height = newHeight; 544 } 545 childViewState.yTranslation = transitioningPositionStart + offset - newHeight 546 - getPaddingAfterChild(algorithmState, child); 547 childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; 548 } 549 550 private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, 551 float transitioningPositionStart, ExpandableViewState childViewState, 552 int collapsedHeight, AmbientState ambientState, ExpandableView child) { 553 float currentYPosition; 554 algorithmState.itemsInBottomStack += 1.0f; 555 if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { 556 // We are visually entering the bottom stack 557 currentYPosition = transitioningPositionStart 558 + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack) 559 - getPaddingAfterChild(algorithmState, child); 560 childViewState.location = ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING; 561 } else { 562 // we are fully inside the stack 563 if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { 564 childViewState.hidden = true; 565 childViewState.shadowAlpha = 0.0f; 566 } else if (algorithmState.itemsInBottomStack 567 > MAX_ITEMS_IN_BOTTOM_STACK + 1) { 568 childViewState.shadowAlpha = 1.0f - algorithmState.partialInBottom; 569 } 570 childViewState.location = ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN; 571 currentYPosition = ambientState.getInnerHeight(); 572 } 573 childViewState.height = collapsedHeight; 574 childViewState.yTranslation = currentYPosition - collapsedHeight; 575 } 576 577 578 /** 579 * Update the height of the first child i.e clamp it to the bottom stack 580 * 581 * @param child the child to update 582 * @param childViewState the viewstate of the child 583 * @param childHeight the height of the child 584 * @param ambientState The ambient state of the algorithm 585 */ 586 protected void updateFirstChildHeight(ExpandableView child, ExpandableViewState childViewState, 587 int childHeight, AmbientState ambientState) { 588 589 // The starting position of the bottom stack peek 590 int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize - 591 mBottomStackSlowDownLength + ambientState.getScrollY(); 592 // Collapse and expand the first child while the shade is being expanded 593 childViewState.height = (int) Math.max(Math.min(bottomPeekStart, (float) childHeight), 594 child.getCollapsedHeight()); 595 } 596 597 /** 598 * Calculate the Z positions for all children based on the number of items in both stacks and 599 * save it in the resultState 600 * @param resultState The result state to update the zTranslation values 601 * @param algorithmState The state in which the current pass of the algorithm is currently in 602 * @param ambientState The ambient state of the algorithm 603 */ 604 private void updateZValuesForState(StackScrollState resultState, 605 StackScrollAlgorithmState algorithmState, AmbientState ambientState) { 606 int childCount = algorithmState.visibleChildren.size(); 607 float childrenOnTop = 0.0f; 608 for (int i = childCount - 1; i >= 0; i--) { 609 updateChildZValue(i, childCount, childrenOnTop, 610 resultState, algorithmState, ambientState); 611 } 612 } 613 614 protected void updateChildZValue(int i, int childCount, float childrenOnTop, 615 StackScrollState resultState, StackScrollAlgorithmState algorithmState, 616 AmbientState ambientState) { 617 ExpandableView child = algorithmState.visibleChildren.get(i); 618 ExpandableViewState childViewState = resultState.getViewStateForView(child); 619 int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); 620 float baseZ = ambientState.getBaseZHeight(); 621 if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) { 622 // We are in the bottom stack 623 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack); 624 float zSubtraction; 625 if (numItemsAbove <= 1.0f) { 626 float factor = 0.2f; 627 // Lets fade in slower to the threshold to make the shadow fade in look nicer 628 if (numItemsAbove <= factor) { 629 zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD 630 * numItemsAbove * (1.0f / factor); 631 } else { 632 zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD 633 + (numItemsAbove - factor) * (1.0f / (1.0f - factor)) 634 * (zDistanceBetweenElements 635 - FakeShadowView.SHADOW_SIBLING_TRESHOLD); 636 } 637 } else { 638 zSubtraction = numItemsAbove * zDistanceBetweenElements; 639 } 640 childViewState.zTranslation = baseZ - zSubtraction; 641 } else if (child.mustStayOnScreen() 642 && childViewState.yTranslation < ambientState.getTopPadding() 643 + ambientState.getStackTranslation()) { 644 if (childrenOnTop != 0.0f) { 645 childrenOnTop++; 646 } else { 647 float overlap = ambientState.getTopPadding() 648 + ambientState.getStackTranslation() - childViewState.yTranslation; 649 childrenOnTop += Math.min(1.0f, overlap / childViewState.height); 650 } 651 childViewState.zTranslation = baseZ 652 + childrenOnTop * zDistanceBetweenElements; 653 } else { 654 childViewState.zTranslation = baseZ; 655 } 656 } 657 658 private boolean isMaxSizeInitialized(ExpandableView child) { 659 if (child instanceof ExpandableNotificationRow) { 660 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 661 return row.isMaxExpandHeightInitialized(); 662 } 663 return child == null || child.getWidth() != 0; 664 } 665 666 private View findFirstVisibleChild(ViewGroup container) { 667 int childCount = container.getChildCount(); 668 for (int i = 0; i < childCount; i++) { 669 View child = container.getChildAt(i); 670 if (child.getVisibility() != View.GONE) { 671 return child; 672 } 673 } 674 return null; 675 } 676 677 public void setIsExpanded(boolean isExpanded) { 678 this.mIsExpanded = isExpanded; 679 } 680 681 public class StackScrollAlgorithmState { 682 683 /** 684 * The scroll position of the algorithm 685 */ 686 public int scrollY; 687 688 /** 689 * The quantity of items which are in the bottom stack. 690 */ 691 public float itemsInBottomStack; 692 693 /** 694 * how far in is the element currently transitioning into the bottom stack 695 */ 696 public float partialInBottom; 697 698 /** 699 * The children from the host view which are not gone. 700 */ 701 public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); 702 703 /** 704 * The children from the host that need an increased padding after them. A value of 0 means 705 * no increased padding, a value of 1 means full padding. 706 */ 707 public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>(); 708 709 public int getPaddingAfterChild(ExpandableView child) { 710 Float paddingValue = increasedPaddingMap.get(child); 711 return paddingValue == null 712 ? mPaddingBetweenElements 713 : (int) NotificationUtils.interpolate(mPaddingBetweenElements, 714 mIncreasedPaddingBetweenElements, 715 paddingValue); 716 } 717 } 718 719} 720