StackScrollAlgorithm.java revision 34c0a8d72aee1867cf7b6d04531c7faec76ab473
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; 27 28import java.util.ArrayList; 29 30/** 31 * The Algorithm of the {@link com.android.systemui.statusbar.stack 32 * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar 33 * .stack.StackScrollState} 34 */ 35public class StackScrollAlgorithm { 36 37 private static final String LOG_TAG = "StackScrollAlgorithm"; 38 39 private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3; 40 private static final int MAX_ITEMS_IN_TOP_STACK = 3; 41 42 /** When a child is activated, the other cards' alpha fade to this value. */ 43 private static final float ACTIVATED_INVERSE_ALPHA = 0.9f; 44 private static final float DIMMED_SCALE = 0.95f; 45 46 private int mPaddingBetweenElements; 47 private int mCollapsedSize; 48 private int mTopStackPeekSize; 49 private int mBottomStackPeekSize; 50 private int mZDistanceBetweenElements; 51 private int mZBasicHeight; 52 53 private StackIndentationFunctor mTopStackIndentationFunctor; 54 private StackIndentationFunctor mBottomStackIndentationFunctor; 55 56 private int mLayoutHeight; 57 58 /** mLayoutHeight - mTopPadding */ 59 private int mInnerHeight; 60 private int mTopPadding; 61 private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); 62 private boolean mIsExpansionChanging; 63 private int mFirstChildMaxHeight; 64 private boolean mIsExpanded; 65 private ExpandableView mFirstChildWhileExpanding; 66 private boolean mExpandedOnStart; 67 private int mTopStackTotalSize; 68 private int mPaddingBetweenElementsDimmed; 69 private int mPaddingBetweenElementsNormal; 70 private int mBottomStackSlowDownLength; 71 72 public StackScrollAlgorithm(Context context) { 73 initConstants(context); 74 updatePadding(false); 75 } 76 77 private void updatePadding(boolean dimmed) { 78 mPaddingBetweenElements = dimmed 79 ? mPaddingBetweenElementsDimmed 80 : mPaddingBetweenElementsNormal; 81 mTopStackTotalSize = mCollapsedSize + mPaddingBetweenElements; 82 mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( 83 MAX_ITEMS_IN_TOP_STACK, 84 mTopStackPeekSize, 85 mTopStackTotalSize, 86 0.5f); 87 mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( 88 MAX_ITEMS_IN_BOTTOM_STACK, 89 mBottomStackPeekSize, 90 getBottomStackSlowDownLength(), 91 0.5f); 92 } 93 94 public int getBottomStackSlowDownLength() { 95 return mBottomStackSlowDownLength + mPaddingBetweenElements; 96 } 97 98 private void initConstants(Context context) { 99 mPaddingBetweenElementsDimmed = context.getResources() 100 .getDimensionPixelSize(R.dimen.notification_padding_dimmed); 101 mPaddingBetweenElementsNormal = context.getResources() 102 .getDimensionPixelSize(R.dimen.notification_padding); 103 mCollapsedSize = context.getResources() 104 .getDimensionPixelSize(R.dimen.notification_min_height); 105 mTopStackPeekSize = context.getResources() 106 .getDimensionPixelSize(R.dimen.top_stack_peek_amount); 107 mBottomStackPeekSize = context.getResources() 108 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 109 mZDistanceBetweenElements = context.getResources() 110 .getDimensionPixelSize(R.dimen.z_distance_between_notifications); 111 mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; 112 mBottomStackSlowDownLength = context.getResources() 113 .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); 114 } 115 116 117 public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) { 118 // The state of the local variables are saved in an algorithmState to easily subdivide it 119 // into multiple phases. 120 StackScrollAlgorithmState algorithmState = mTempAlgorithmState; 121 122 // First we reset the view states to their default values. 123 resultState.resetViewStates(); 124 125 algorithmState.itemsInTopStack = 0.0f; 126 algorithmState.partialInTop = 0.0f; 127 algorithmState.lastTopStackIndex = 0; 128 algorithmState.scrolledPixelsTop = 0; 129 algorithmState.itemsInBottomStack = 0.0f; 130 algorithmState.partialInBottom = 0.0f; 131 algorithmState.scrollY = ambientState.getScrollY() + mCollapsedSize; 132 133 updateVisibleChildren(resultState, algorithmState); 134 135 // Phase 1: 136 findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState); 137 138 // Phase 2: 139 updatePositionsForState(resultState, algorithmState); 140 141 // Phase 3: 142 updateZValuesForState(resultState, algorithmState); 143 144 handleDraggedViews(ambientState, resultState, algorithmState); 145 updateDimmedActivated(ambientState, resultState, algorithmState); 146 } 147 148 /** 149 * Updates the dimmed and activated states of the children. 150 */ 151 private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState, 152 StackScrollAlgorithmState algorithmState) { 153 boolean dimmed = ambientState.isDimmed(); 154 View activatedChild = ambientState.getActivatedChild(); 155 int childCount = algorithmState.visibleChildren.size(); 156 for (int i = 0; i < childCount; i++) { 157 View child = algorithmState.visibleChildren.get(i); 158 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 159 childViewState.dimmed = dimmed; 160 childViewState.scale = !dimmed || activatedChild == child 161 ? 1.0f 162 : DIMMED_SCALE; 163 if (dimmed && activatedChild != null && child != activatedChild) { 164 childViewState.alpha *= ACTIVATED_INVERSE_ALPHA; 165 } 166 } 167 } 168 169 /** 170 * Handle the special state when views are being dragged 171 */ 172 private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState, 173 StackScrollAlgorithmState algorithmState) { 174 ArrayList<View> draggedViews = ambientState.getDraggedViews(); 175 for (View draggedView : draggedViews) { 176 int childIndex = algorithmState.visibleChildren.indexOf(draggedView); 177 if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) { 178 View nextChild = algorithmState.visibleChildren.get(childIndex + 1); 179 if (!draggedViews.contains(nextChild)) { 180 // only if the view is not dragged itself we modify its state to be fully 181 // visible 182 StackScrollState.ViewState viewState = resultState.getViewStateForView( 183 nextChild); 184 // The child below the dragged one must be fully visible 185 viewState.alpha = 1; 186 } 187 188 // Lets set the alpha to the one it currently has, as its currently being dragged 189 StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView); 190 // The dragged child should keep the set alpha 191 viewState.alpha = draggedView.getAlpha(); 192 } 193 } 194 } 195 196 /** 197 * Update the visible children on the state. 198 */ 199 private void updateVisibleChildren(StackScrollState resultState, 200 StackScrollAlgorithmState state) { 201 ViewGroup hostView = resultState.getHostView(); 202 int childCount = hostView.getChildCount(); 203 state.visibleChildren.clear(); 204 state.visibleChildren.ensureCapacity(childCount); 205 for (int i = 0; i < childCount; i++) { 206 ExpandableView v = (ExpandableView) hostView.getChildAt(i); 207 if (v.getVisibility() != View.GONE) { 208 state.visibleChildren.add(v); 209 } 210 } 211 } 212 213 /** 214 * Determine the positions for the views. This is the main part of the algorithm. 215 * 216 * @param resultState The result state to update if a change to the properties of a child occurs 217 * @param algorithmState The state in which the current pass of the algorithm is currently in 218 * and which will be updated 219 */ 220 private void updatePositionsForState(StackScrollState resultState, 221 StackScrollAlgorithmState algorithmState) { 222 223 // The starting position of the bottom stack peek 224 float bottomPeekStart = mInnerHeight - mBottomStackPeekSize; 225 226 // The position where the bottom stack starts. 227 float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength; 228 229 // The y coordinate of the current child. 230 float currentYPosition = 0.0f; 231 232 // How far in is the element currently transitioning into the bottom stack. 233 float yPositionInScrollView = 0.0f; 234 235 int childCount = algorithmState.visibleChildren.size(); 236 int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; 237 for (int i = 0; i < childCount; i++) { 238 ExpandableView child = algorithmState.visibleChildren.get(i); 239 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 240 childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN; 241 int childHeight = getMaxAllowedChildHeight(child); 242 float yPositionInScrollViewAfterElement = yPositionInScrollView 243 + childHeight 244 + mPaddingBetweenElements; 245 float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize; 246 247 if (i == algorithmState.lastTopStackIndex + 1) { 248 // Normally the position of this child is the position in the regular scrollview, 249 // but if the two stacks are very close to each other, 250 // then have have to push it even more upwards to the position of the bottom 251 // stack start. 252 currentYPosition = Math.min(scrollOffset, bottomStackStart); 253 } 254 childViewState.yTranslation = currentYPosition; 255 256 // The y position after this element 257 float nextYPosition = currentYPosition + childHeight + 258 mPaddingBetweenElements; 259 260 if (i <= algorithmState.lastTopStackIndex) { 261 // Case 1: 262 // We are in the top Stack 263 updateStateForTopStackChild(algorithmState, 264 numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset); 265 clampYTranslation(childViewState, childHeight); 266 // check if we are overlapping with the bottom stack 267 if (childViewState.yTranslation + childHeight + mPaddingBetweenElements 268 >= bottomStackStart && !mIsExpansionChanging && i != 0) { 269 // TODO: handle overlapping sizes with end stack better 270 // we just collapse this element 271 childViewState.height = mCollapsedSize; 272 } 273 } else if (nextYPosition >= bottomStackStart) { 274 // Case 2: 275 // We are in the bottom stack. 276 if (currentYPosition >= bottomStackStart) { 277 // According to the regular scroll view we are fully translated out of the 278 // bottom of the screen so we are fully in the bottom stack 279 updateStateForChildFullyInBottomStack(algorithmState, 280 bottomStackStart, childViewState, childHeight); 281 } else { 282 // According to the regular scroll view we are currently translating out of / 283 // into the bottom of the screen 284 updateStateForChildTransitioningInBottom(algorithmState, 285 bottomStackStart, bottomPeekStart, currentYPosition, 286 childViewState, childHeight); 287 } 288 } else { 289 // Case 3: 290 // We are in the regular scroll area. 291 childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; 292 clampYTranslation(childViewState, childHeight); 293 } 294 295 // The first card is always rendered. 296 if (i == 0) { 297 childViewState.alpha = 1.0f; 298 childViewState.yTranslation = 0; 299 childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD; 300 } 301 if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) { 302 Log.wtf(LOG_TAG, "Failed to assign location for child " + i); 303 } 304 currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; 305 yPositionInScrollView = yPositionInScrollViewAfterElement; 306 307 childViewState.yTranslation += mTopPadding; 308 } 309 } 310 311 /** 312 * Clamp the yTranslation both up and down to valid positions. 313 * 314 * @param childViewState the view state of the child 315 * @param childHeight the height of this child 316 */ 317 private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) { 318 clampPositionToBottomStackStart(childViewState, childHeight); 319 clampPositionToTopStackEnd(childViewState, childHeight); 320 } 321 322 /** 323 * Clamp the yTranslation of the child down such that its end is at most on the beginning of 324 * the bottom stack. 325 * 326 * @param childViewState the view state of the child 327 * @param childHeight the height of this child 328 */ 329 private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState, 330 int childHeight) { 331 childViewState.yTranslation = Math.min(childViewState.yTranslation, 332 mInnerHeight - mBottomStackPeekSize - childHeight); 333 } 334 335 /** 336 * Clamp the yTranslation of the child up such that its end is at lest on the end of the top 337 * stack.get 338 * 339 * @param childViewState the view state of the child 340 * @param childHeight the height of this child 341 */ 342 private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState, 343 int childHeight) { 344 childViewState.yTranslation = Math.max(childViewState.yTranslation, 345 mCollapsedSize - childHeight); 346 } 347 348 private int getMaxAllowedChildHeight(View child) { 349 if (child instanceof ExpandableNotificationRow) { 350 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 351 return row.getIntrinsicHeight(); 352 } else if (child instanceof ExpandableView) { 353 ExpandableView expandableView = (ExpandableView) child; 354 return expandableView.getActualHeight(); 355 } 356 return child == null? mCollapsedSize : child.getHeight(); 357 } 358 359 private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, 360 float transitioningPositionStart, float bottomPeakStart, float currentYPosition, 361 StackScrollState.ViewState childViewState, int childHeight) { 362 363 // This is the transitioning element on top of bottom stack, calculate how far we are in. 364 algorithmState.partialInBottom = 1.0f - ( 365 (transitioningPositionStart - currentYPosition) / (childHeight + 366 mPaddingBetweenElements)); 367 368 // the offset starting at the transitionPosition of the bottom stack 369 float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom); 370 algorithmState.itemsInBottomStack += algorithmState.partialInBottom; 371 childViewState.yTranslation = transitioningPositionStart + offset - childHeight 372 - mPaddingBetweenElements; 373 374 // We want at least to be at the end of the top stack when collapsing 375 clampPositionToTopStackEnd(childViewState, childHeight); 376 childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; 377 } 378 379 private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, 380 float transitioningPositionStart, StackScrollState.ViewState childViewState, 381 int childHeight) { 382 383 float currentYPosition; 384 algorithmState.itemsInBottomStack += 1.0f; 385 if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { 386 // We are visually entering the bottom stack 387 currentYPosition = transitioningPositionStart 388 + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack) 389 - mPaddingBetweenElements; 390 childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING; 391 } else { 392 // we are fully inside the stack 393 if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { 394 childViewState.alpha = 0.0f; 395 } else if (algorithmState.itemsInBottomStack 396 > MAX_ITEMS_IN_BOTTOM_STACK + 1) { 397 childViewState.alpha = 1.0f - algorithmState.partialInBottom; 398 } 399 childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN; 400 currentYPosition = mInnerHeight; 401 } 402 childViewState.yTranslation = currentYPosition - childHeight; 403 clampPositionToTopStackEnd(childViewState, childHeight); 404 } 405 406 private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState, 407 int numberOfElementsCompletelyIn, int i, int childHeight, 408 StackScrollState.ViewState childViewState, float scrollOffset) { 409 410 411 // First we calculate the index relative to the current stack window of size at most 412 // {@link #MAX_ITEMS_IN_TOP_STACK} 413 int paddedIndex = i - 1 414 - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0); 415 if (paddedIndex >= 0) { 416 417 // We are currently visually entering the top stack 418 float distanceToStack = childHeight - algorithmState.scrolledPixelsTop; 419 if (i == algorithmState.lastTopStackIndex && distanceToStack > mTopStackTotalSize) { 420 421 // Child is currently translating into stack but not yet inside slow down zone. 422 // Handle it like the regular scrollview. 423 childViewState.yTranslation = scrollOffset; 424 } else { 425 // Apply stacking logic. 426 float numItemsBefore; 427 if (i == algorithmState.lastTopStackIndex) { 428 numItemsBefore = 1.0f - (distanceToStack / mTopStackTotalSize); 429 } else { 430 numItemsBefore = algorithmState.itemsInTopStack - i; 431 } 432 // The end position of the current child 433 float currentChildEndY = mCollapsedSize + mTopStackTotalSize - 434 mTopStackIndentationFunctor.getValue(numItemsBefore); 435 childViewState.yTranslation = currentChildEndY - childHeight; 436 } 437 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING; 438 } else { 439 if (paddedIndex == -1) { 440 childViewState.alpha = 1.0f - algorithmState.partialInTop; 441 } else { 442 // We are hidden behind the top card and faded out, so we can hide ourselves. 443 childViewState.alpha = 0.0f; 444 } 445 childViewState.yTranslation = mCollapsedSize - childHeight; 446 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN; 447 } 448 449 450 } 451 452 /** 453 * Find the number of items in the top stack and update the result state if needed. 454 * 455 * @param resultState The result state to update if a height change of an child occurs 456 * @param algorithmState The state in which the current pass of the algorithm is currently in 457 * and which will be updated 458 */ 459 private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState, 460 StackScrollAlgorithmState algorithmState) { 461 462 // The y Position if the element would be in a regular scrollView 463 float yPositionInScrollView = 0.0f; 464 int childCount = algorithmState.visibleChildren.size(); 465 466 // find the number of elements in the top stack. 467 for (int i = 0; i < childCount; i++) { 468 ExpandableView child = algorithmState.visibleChildren.get(i); 469 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 470 int childHeight = getMaxAllowedChildHeight(child); 471 float yPositionInScrollViewAfterElement = yPositionInScrollView 472 + childHeight 473 + mPaddingBetweenElements; 474 if (yPositionInScrollView < algorithmState.scrollY) { 475 if (i == 0 && algorithmState.scrollY == mCollapsedSize) { 476 477 // The starting position of the bottom stack peek 478 int bottomPeekStart = mInnerHeight - mBottomStackPeekSize; 479 // Collapse and expand the first child while the shade is being expanded 480 float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding 481 ? mFirstChildMaxHeight 482 : childHeight; 483 childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight), 484 mCollapsedSize); 485 algorithmState.itemsInTopStack = 1.0f; 486 487 } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) { 488 // According to the regular scroll view we are fully off screen 489 algorithmState.itemsInTopStack += 1.0f; 490 if (i == 0) { 491 childViewState.height = mCollapsedSize; 492 } 493 } else { 494 // According to the regular scroll view we are partially off screen 495 // If it is expanded we have to collapse it to a new size 496 float newSize = yPositionInScrollViewAfterElement 497 - mPaddingBetweenElements 498 - algorithmState.scrollY; 499 500 if (i == 0) { 501 newSize += mCollapsedSize; 502 } 503 504 // How much did we scroll into this child 505 algorithmState.scrolledPixelsTop = childHeight - newSize; 506 algorithmState.partialInTop = (algorithmState.scrolledPixelsTop) / (childHeight 507 + mPaddingBetweenElements); 508 509 // Our element can be expanded, so this can get negative 510 algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop); 511 algorithmState.itemsInTopStack += algorithmState.partialInTop; 512 newSize = Math.max(mCollapsedSize, newSize); 513 if (i == 0) { 514 childViewState.height = (int) newSize; 515 } 516 algorithmState.lastTopStackIndex = i; 517 break; 518 } 519 } else { 520 algorithmState.lastTopStackIndex = i - 1; 521 // We are already past the stack so we can end the loop 522 break; 523 } 524 yPositionInScrollView = yPositionInScrollViewAfterElement; 525 } 526 } 527 528 /** 529 * Calculate the Z positions for all children based on the number of items in both stacks and 530 * save it in the resultState 531 * 532 * @param resultState The result state to update the zTranslation values 533 * @param algorithmState The state in which the current pass of the algorithm is currently in 534 */ 535 private void updateZValuesForState(StackScrollState resultState, 536 StackScrollAlgorithmState algorithmState) { 537 int childCount = algorithmState.visibleChildren.size(); 538 for (int i = 0; i < childCount; i++) { 539 View child = algorithmState.visibleChildren.get(i); 540 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 541 if (i < algorithmState.itemsInTopStack) { 542 float stackIndex = algorithmState.itemsInTopStack - i; 543 stackIndex = Math.min(stackIndex, MAX_ITEMS_IN_TOP_STACK + 2); 544 childViewState.zTranslation = mZBasicHeight 545 + stackIndex * mZDistanceBetweenElements; 546 } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) { 547 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack); 548 float translationZ = mZBasicHeight 549 - numItemsAbove * mZDistanceBetweenElements; 550 childViewState.zTranslation = translationZ; 551 } else { 552 childViewState.zTranslation = mZBasicHeight; 553 } 554 } 555 } 556 557 public void setLayoutHeight(int layoutHeight) { 558 this.mLayoutHeight = layoutHeight; 559 updateInnerHeight(); 560 } 561 562 public void setTopPadding(int topPadding) { 563 mTopPadding = topPadding; 564 updateInnerHeight(); 565 } 566 567 private void updateInnerHeight() { 568 mInnerHeight = mLayoutHeight - mTopPadding; 569 } 570 571 public void onExpansionStarted(StackScrollState currentState) { 572 mIsExpansionChanging = true; 573 mExpandedOnStart = mIsExpanded; 574 ViewGroup hostView = currentState.getHostView(); 575 updateFirstChildHeightWhileExpanding(hostView); 576 } 577 578 private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) { 579 mFirstChildWhileExpanding = (ExpandableView) findFirstVisibleChild(hostView); 580 if (mFirstChildWhileExpanding != null) { 581 if (mExpandedOnStart) { 582 583 // We are collapsing the shade, so the first child can get as most as high as the 584 // current height. 585 mFirstChildMaxHeight = mFirstChildWhileExpanding.getActualHeight(); 586 } else { 587 588 // We are expanding the shade, expand it to its full height. 589 if (mFirstChildWhileExpanding.getWidth() == 0) { 590 591 // This child was not layouted yet, wait for a layout pass 592 mFirstChildWhileExpanding 593 .addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 594 @Override 595 public void onLayoutChange(View v, int left, int top, int right, 596 int bottom, int oldLeft, int oldTop, int oldRight, 597 int oldBottom) { 598 if (mFirstChildWhileExpanding != null) { 599 mFirstChildMaxHeight = getMaxAllowedChildHeight( 600 mFirstChildWhileExpanding); 601 } else { 602 mFirstChildMaxHeight = 0; 603 } 604 v.removeOnLayoutChangeListener(this); 605 } 606 }); 607 } else { 608 mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); 609 } 610 } 611 } else { 612 mFirstChildMaxHeight = 0; 613 } 614 } 615 616 private View findFirstVisibleChild(ViewGroup container) { 617 int childCount = container.getChildCount(); 618 for (int i = 0; i < childCount; i++) { 619 View child = container.getChildAt(i); 620 if (child.getVisibility() != View.GONE) { 621 return child; 622 } 623 } 624 return null; 625 } 626 627 public void onExpansionStopped() { 628 mIsExpansionChanging = false; 629 mFirstChildWhileExpanding = null; 630 } 631 632 public void setIsExpanded(boolean isExpanded) { 633 this.mIsExpanded = isExpanded; 634 } 635 636 public void notifyChildrenChanged(ViewGroup hostView) { 637 if (mIsExpansionChanging) { 638 updateFirstChildHeightWhileExpanding(hostView); 639 } 640 } 641 642 public void setDimmed(boolean dimmed) { 643 updatePadding(dimmed); 644 } 645 646 class StackScrollAlgorithmState { 647 648 /** 649 * The scroll position of the algorithm 650 */ 651 public int scrollY; 652 653 /** 654 * The quantity of items which are in the top stack. 655 */ 656 public float itemsInTopStack; 657 658 /** 659 * how far in is the element currently transitioning into the top stack 660 */ 661 public float partialInTop; 662 663 /** 664 * The number of pixels the last child in the top stack has scrolled in to the stack 665 */ 666 public float scrolledPixelsTop; 667 668 /** 669 * The last item index which is in the top stack. 670 */ 671 public int lastTopStackIndex; 672 673 /** 674 * The quantity of items which are in the bottom stack. 675 */ 676 public float itemsInBottomStack; 677 678 /** 679 * how far in is the element currently transitioning into the bottom stack 680 */ 681 public float partialInBottom; 682 683 /** 684 * The children from the host view which are not gone. 685 */ 686 public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); 687 } 688 689} 690