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