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