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