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