StackScrollAlgorithm.java revision 6e3ecebcec1b82fd81f6d78b8deb5c4189b6026e
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; 23import com.android.systemui.R; 24 25/** 26 * The Algorithm of the {@link com.android.systemui.statusbar.stack 27 * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar 28 * .stack.StackScrollState} 29 */ 30public class StackScrollAlgorithm { 31 32 private static final String LOG_TAG = "StackScrollAlgorithm"; 33 34 private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3; 35 private static final int MAX_ITEMS_IN_TOP_STACK = 3; 36 37 private int mPaddingBetweenElements; 38 private int mCollapsedSize; 39 private int mTopStackPeekSize; 40 private int mBottomStackPeekSize; 41 private int mZDistanceBetweenElements; 42 private int mZBasicHeight; 43 44 private StackIndentationFunctor mTopStackIndentationFunctor; 45 private StackIndentationFunctor mBottomStackIndentationFunctor; 46 47 private float mLayoutHeight; 48 private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); 49 50 public StackScrollAlgorithm(Context context) { 51 initConstants(context); 52 } 53 54 private void initConstants(Context context) { 55 56 // currently the padding is in the elements themself 57 mPaddingBetweenElements = 0; 58 mCollapsedSize = context.getResources() 59 .getDimensionPixelSize(R.dimen.notification_row_min_height); 60 mTopStackPeekSize = context.getResources() 61 .getDimensionPixelSize(R.dimen.top_stack_peek_amount); 62 mBottomStackPeekSize = context.getResources() 63 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 64 mZDistanceBetweenElements = context.getResources() 65 .getDimensionPixelSize(R.dimen.z_distance_between_notifications); 66 mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; 67 68 mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( 69 MAX_ITEMS_IN_TOP_STACK, 70 mTopStackPeekSize, 71 mCollapsedSize + mPaddingBetweenElements, 72 0.5f); 73 mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( 74 MAX_ITEMS_IN_BOTTOM_STACK, 75 mBottomStackPeekSize, 76 mBottomStackPeekSize, 77 0.5f); 78 } 79 80 81 public void getStackScrollState(StackScrollState resultState) { 82 // The state of the local variables are saved in an algorithmState to easily subdivide it 83 // into multiple phases. 84 StackScrollAlgorithmState algorithmState = mTempAlgorithmState; 85 86 // First we reset the view states to their default values. 87 resultState.resetViewStates(); 88 89 // The first element is always in there so it's initialized with 1.0f. 90 algorithmState.itemsInTopStack = 1.0f; 91 algorithmState.partialInTop = 0.0f; 92 algorithmState.lastTopStackIndex = 0; 93 algorithmState.scrollY = resultState.getScrollY(); 94 algorithmState.itemsInBottomStack = 0.0f; 95 96 // Phase 1: 97 findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState); 98 99 // Phase 2: 100 updatePositionsForState(resultState, algorithmState); 101 102 // Phase 3: 103 updateZValuesForState(resultState, algorithmState); 104 105 // Write the algorithm state to the result. 106 resultState.setScrollY(algorithmState.scrollY); 107 } 108 109 /** 110 * Determine the positions for the views. This is the main part of the algorithm. 111 * 112 * @param resultState The result state to update if a change to the properties of a child occurs 113 * @param algorithmState The state in which the current pass of the algorithm is currently in 114 * and which will be updated 115 */ 116 private void updatePositionsForState(StackScrollState resultState, 117 StackScrollAlgorithmState algorithmState) { 118 float stackHeight = getLayoutHeight(); 119 120 // The position where the bottom stack starts. 121 float transitioningPositionStart = stackHeight - mCollapsedSize - mBottomStackPeekSize; 122 123 // The y coordinate of the current child. 124 float currentYPosition = 0.0f; 125 126 // How far in is the element currently transitioning into the bottom stack. 127 float yPositionInScrollView = 0.0f; 128 129 ViewGroup hostView = resultState.getHostView(); 130 int childCount = hostView.getChildCount(); 131 int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; 132 for (int i = 0; i < childCount; i++) { 133 View child = hostView.getChildAt(i); 134 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 135 childViewState.yTranslation = currentYPosition; 136 childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN; 137 int childHeight = child.getHeight(); 138 // The y position after this element 139 float nextYPosition = currentYPosition + childHeight + mPaddingBetweenElements; 140 float yPositionInScrollViewAfterElement = yPositionInScrollView 141 + childHeight 142 + mPaddingBetweenElements; 143 float scrollOffset = yPositionInScrollViewAfterElement - algorithmState.scrollY; 144 if (i < algorithmState.lastTopStackIndex) { 145 // Case 1: 146 // We are in the top Stack 147 nextYPosition = updateStateForTopStackChild(algorithmState, 148 numberOfElementsCompletelyIn, 149 i, childViewState); 150 } else if (i == algorithmState.lastTopStackIndex) { 151 // Case 2: 152 // First element of regular scrollview comes next, so the position is just the 153 // scrolling position 154 nextYPosition = scrollOffset; 155 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING; 156 } else if (nextYPosition >= transitioningPositionStart) { 157 if (currentYPosition >= transitioningPositionStart) { 158 // Case 3: 159 // According to the regular scroll view we are fully translated out of the 160 // bottom of the screen so we are fully in the bottom stack 161 nextYPosition = updateStateForChildFullyInBottomStack(algorithmState, 162 transitioningPositionStart, childViewState, childHeight); 163 } else { 164 // Case 4: 165 // According to the regular scroll view we are currently translating out of / 166 // into the bottom of the screen 167 nextYPosition = updateStateForChildTransitioningInBottom( 168 algorithmState, stackHeight, transitioningPositionStart, 169 currentYPosition, childViewState, 170 childHeight, nextYPosition); 171 } 172 } else { 173 childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; 174 } 175 // The first card is always rendered. 176 if (i == 0) { 177 childViewState.alpha = 1.0f; 178 childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD; 179 } 180 if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) { 181 Log.wtf(LOG_TAG, "Failed to assign location for child " + i); 182 } 183 currentYPosition = nextYPosition; 184 yPositionInScrollView = yPositionInScrollViewAfterElement; 185 } 186 } 187 188 private float updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, 189 float stackHeight, float transitioningPositionStart, float currentYPosition, 190 StackScrollState.ViewState childViewState, int childHeight, float nextYPosition) { 191 float newSize = transitioningPositionStart + mCollapsedSize - currentYPosition; 192 newSize = Math.min(childHeight, newSize); 193 // Transitioning element on top of bottom stack: 194 algorithmState.partialInBottom = 1.0f - ( 195 (stackHeight - mBottomStackPeekSize - nextYPosition) / mCollapsedSize); 196 // Our element can be expanded, so we might even have to scroll further than 197 // mCollapsedSize 198 algorithmState.partialInBottom = Math.min(1.0f, algorithmState.partialInBottom); 199 float offset = mBottomStackIndentationFunctor.getValue( 200 algorithmState.partialInBottom); 201 nextYPosition = transitioningPositionStart + offset; 202 algorithmState.itemsInBottomStack += algorithmState.partialInBottom; 203 // TODO: only temporarily collapse 204 if (childHeight != (int) newSize) { 205 childViewState.height = (int) newSize; 206 } 207 childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; 208 209 return nextYPosition; 210 } 211 212 private float updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, 213 float transitioningPositionStart, StackScrollState.ViewState childViewState, 214 int childHeight) { 215 216 float nextYPosition; 217 algorithmState.itemsInBottomStack += 1.0f; 218 if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { 219 // We are visually entering the bottom stack 220 nextYPosition = transitioningPositionStart 221 + mBottomStackIndentationFunctor.getValue( 222 algorithmState.itemsInBottomStack); 223 childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING; 224 } else { 225 // we are fully inside the stack 226 if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { 227 childViewState.alpha = 0.0f; 228 } else if (algorithmState.itemsInBottomStack 229 > MAX_ITEMS_IN_BOTTOM_STACK + 1) { 230 childViewState.alpha = 1.0f - algorithmState.partialInBottom; 231 } 232 childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN; 233 nextYPosition = transitioningPositionStart + mBottomStackPeekSize; 234 } 235 // TODO: only temporarily collapse 236 if (childHeight != mCollapsedSize) { 237 childViewState.height = mCollapsedSize; 238 } 239 return nextYPosition; 240 } 241 242 private float updateStateForTopStackChild(StackScrollAlgorithmState algorithmState, 243 int numberOfElementsCompletelyIn, int i, StackScrollState.ViewState childViewState) { 244 245 float nextYPosition = 0; 246 247 // First we calculate the index relative to the current stack window of size at most 248 // {@link #MAX_ITEMS_IN_TOP_STACK} 249 int paddedIndex = i 250 - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0); 251 if (paddedIndex >= 0) { 252 // We are currently visually entering the top stack 253 nextYPosition = mCollapsedSize + mPaddingBetweenElements - 254 mTopStackIndentationFunctor.getValue( 255 algorithmState.itemsInTopStack - i - 1); 256 if (paddedIndex == 0) { 257 childViewState.alpha = 1.0f - algorithmState.partialInTop; 258 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN; 259 } else { 260 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING; 261 } 262 } else { 263 // We are hidden behind the top card and faded out, so we can hide ourselves. 264 childViewState.alpha = 0.0f; 265 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN; 266 } 267 return nextYPosition; 268 } 269 270 /** 271 * Find the number of items in the top stack and update the result state if needed. 272 * 273 * @param resultState The result state to update if a height change of an child occurs 274 * @param algorithmState The state in which the current pass of the algorithm is currently in 275 * and which will be updated 276 */ 277 private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState, 278 StackScrollAlgorithmState algorithmState) { 279 280 // The y Position if the element would be in a regular scrollView 281 float yPositionInScrollView = 0.0f; 282 ViewGroup hostView = resultState.getHostView(); 283 int childCount = hostView.getChildCount(); 284 285 // find the number of elements in the top stack. 286 for (int i = 0; i < childCount; i++) { 287 View child = hostView.getChildAt(i); 288 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 289 int childHeight = child.getHeight(); 290 float yPositionInScrollViewAfterElement = yPositionInScrollView 291 + childHeight 292 + mPaddingBetweenElements; 293 if (yPositionInScrollView < algorithmState.scrollY) { 294 if (yPositionInScrollViewAfterElement <= algorithmState.scrollY) { 295 // According to the regular scroll view we are fully off screen 296 algorithmState.itemsInTopStack += 1.0f; 297 if (childHeight != mCollapsedSize) { 298 childViewState.height = mCollapsedSize; 299 } 300 } else { 301 // According to the regular scroll view we are partially off screen 302 // If it is expanded we have to collapse it to a new size 303 float newSize = yPositionInScrollViewAfterElement 304 - mPaddingBetweenElements 305 - algorithmState.scrollY; 306 307 // How much did we scroll into this child 308 algorithmState.partialInTop = (mCollapsedSize - newSize) / (mCollapsedSize 309 + mPaddingBetweenElements); 310 311 // Our element can be expanded, so this can get negative 312 algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop); 313 algorithmState.itemsInTopStack += algorithmState.partialInTop; 314 // TODO: handle overlapping sizes with end stack 315 newSize = Math.max(mCollapsedSize, newSize); 316 // TODO: only temporarily collapse 317 if (newSize != childHeight) { 318 childViewState.height = (int) newSize; 319 320 // We decrease scrollY by the same amount we made this child smaller. 321 // The new scroll position is therefore the start of the element 322 algorithmState.scrollY = (int) yPositionInScrollView; 323 resultState.setScrollY(algorithmState.scrollY); 324 } 325 if (childHeight > mCollapsedSize) { 326 // If we are just resizing this child, this element is not treated to be 327 // transitioning into the stack and therefore it is the last element in 328 // the stack. 329 algorithmState.lastTopStackIndex = i; 330 break; 331 } 332 } 333 } else { 334 algorithmState.lastTopStackIndex = i; 335 336 // We are already past the stack so we can end the loop 337 break; 338 } 339 yPositionInScrollView = yPositionInScrollViewAfterElement; 340 } 341 } 342 343 /** 344 * Calculate the Z positions for all children based on the number of items in both stacks and 345 * save it in the resultState 346 * 347 * @param resultState The result state to update the zTranslation values 348 * @param algorithmState The state in which the current pass of the algorithm is currently in 349 */ 350 private void updateZValuesForState(StackScrollState resultState, 351 StackScrollAlgorithmState algorithmState) { 352 ViewGroup hostView = resultState.getHostView(); 353 int childCount = hostView.getChildCount(); 354 for (int i = 0; i < childCount; i++) { 355 View child = hostView.getChildAt(i); 356 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); 357 if (i < algorithmState.itemsInTopStack) { 358 float stackIndex = algorithmState.itemsInTopStack - i; 359 stackIndex = Math.min(stackIndex, MAX_ITEMS_IN_TOP_STACK + 2); 360 childViewState.zTranslation = mZBasicHeight 361 + stackIndex * mZDistanceBetweenElements; 362 } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) { 363 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack); 364 float translationZ = mZBasicHeight 365 - numItemsAbove * mZDistanceBetweenElements; 366 childViewState.zTranslation = translationZ; 367 } else { 368 childViewState.zTranslation = mZBasicHeight; 369 } 370 } 371 } 372 373 public float getLayoutHeight() { 374 return mLayoutHeight; 375 } 376 377 public void setLayoutHeight(float layoutHeight) { 378 this.mLayoutHeight = layoutHeight; 379 } 380 381 class StackScrollAlgorithmState { 382 383 /** 384 * The scroll position of the algorithm 385 */ 386 public int scrollY; 387 388 /** 389 * The quantity of items which are in the top stack. 390 */ 391 public float itemsInTopStack; 392 393 /** 394 * how far in is the element currently transitioning into the top stack 395 */ 396 public float partialInTop; 397 398 /** 399 * The last item index which is in the top stack. 400 * NOTE: In the top stack the item after the transitioning element is also in the stack! 401 * This is needed to ensure a smooth transition between the y position in the regular 402 * scrollview and the one in the stack. 403 */ 404 public int lastTopStackIndex; 405 406 /** 407 * The quantity of items which are in the bottom stack. 408 */ 409 public float itemsInBottomStack; 410 411 /** 412 * how far in is the element currently transitioning into the bottom stack 413 */ 414 public float partialInBottom; 415 } 416 417} 418